Promise对象用于表示一个异步操作的最终状态(成功/失败)及其结果值。 —— MDN
Promise的出现使我们可以优雅地处理异步操作,脱离回调地狱的痛苦。
有一利必有一弊,它成为了面试必考问题之一 ,转化为另一种痛苦生活在我们身边... 当然,这是句玩笑话~ 🤣
上次看Promise大概一个多月前了,花了两天的空暇时间基于Promise/A+手写了一遍,大多云里雾里。为了巩固,趁着周末又写了一遍,彻底梳理清楚✌。本文主要还是拆解实现Promise丐版及其周边。
基本特点
- 新建Promise会立即执行,无法中断。
- Promise有三种状态:
pengding、fulfilled、rejected。只有pengding -> fulfilled和pending -> rejected两种状态流,且状态更改后,不可再次更改。 resolve为成功状态;reject为失败状态。
Promise基本使用👇
const p = new Promise((resolve, reject) => {
resolve('success')
reject('error')
})
p.then(value => {
console.log(value)
}, reason => {
console.log(reason)
})
// success
拆解实现Promise丐版
实现基本逻辑
// promise.js
const PENGDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
let that = this
// 初始状态为pengding
this.status = PENGDING
// 记录成功返回的结果值
this.value = null
// 记录失败返回的结果值
this.reason = null
function resolve(value) {
if (that.status === PENGDING) {
that.status = FULFILLED
that.value = value
}
}
function reject(reason) {
if (that.status === PENGDING) {
that.status = REJECTED
that.reason = reason
}
}
executor(resolve, reject)
}
Promise.prototype.then = function (onFulfilled, onRejected) {
if (this.status === FULFILLED) {
// 调用成功回调
onFulfilled(this.value)
} else if (this.status === REJECTED) {
// 调用失败回调
onRejected(this.reason)
}
}
module.exports = Promise
引入实现的丐版Promise,执行下上述 🌰
// main.js
const Promise = require('./promise')
const p = new Promise((resolve, reject) => {
resolve('success')
reject('error')
})
p.then(
(value) => {
console.log(value)
},
(reason) => {
console.log(reason)
}
)
执行打印出success。
处理异步情况
// main.js
const Promise = require('./promise')
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
})
})
p.then(
(value) => {
console.log(value)
},
(reason) => {
console.log(reason)
}
)
执行无输出结果,打印then函数的this.status发现状态仍为pengding。
这是为什么呢? 知道事件循环的朋友们应该知道setTimeout是异步且属于宏任务,将会在下个事件循环宏任务中执行,而p.then为当前宏任务下的同步代码。因此,that.status仍为pengding,所以无输出结果。
既然这样,我们需要添加两个字段onFulfilled和onRejected去缓存成功和失败的回调,在resolve或reject时候再去执行缓存函数。
// promise.js
const PENGDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
let that = this
// 初始状态为pengding
this.status = PENGDING
// 记录成功返回的结果值
this.value = null
// 记录失败返回的结果值
this.reason = null
this.onFulfilled = null
this.onRejected = null
function resolve(value) {
if (that.status === PENGDING) {
that.status = FULFILLED
that.value = value
// 有缓存成功回调,则执行
that.onFulfilled && that.onFulfilled(value)
}
}
function reject(reason) {
if (that.status === PENGDING) {
that.status = REJECTED
that.reason = reason
// 有缓存失败回调,则执行
that.onRejected && that.onRejected(value)
}
}
executor(resolve, reject)
}
Promise.prototype.then = function (onFulfilled, onRejected) {
if (this.status === FULFILLED) {
// 调用成功回调
onFulfilled(this.value)
} else if (this.status === REJECTED) {
// 调用失败回调
onRejected(this.reason)
} else {
// 挂载状态下缓存成功和失败回调
this.onFulfilled = onFulfilled
this.onRejected = onRejected
}
}
module.exports = Promise
改进后,执行上述异步情况代码,成功打印success。
再在异步情况下加点料🍕,多次执行不同的p.then函数。
// main.js
const Promise = require('./promise')
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
})
})
p.then(
(value) => {
console.log('第一次', value)
},
(reason) => {
console.log(reason)
}
)
p.then(
(value) => {
console.log('第二次', value)
},
(reason) => {
console.log(reason)
}
)
// 第二次 success
发现我们第一个p.then代码没有被执行。
来分析下原因:目前我们只用一个变量去存储成功和失败的回调,当我setTimeout执行之前,我的两个p.then已按顺序执行完毕。那么,第二个p.then就将覆盖第一个p.then所赋值的回调函数,所以执行结果为第二次 success。
改进下代码,我们用数组的方式去存储所有的回调函数。
const PENGDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
let that = this
// 初始状态为pengding
this.status = PENGDING
// 记录成功返回的结果值
this.value = null
// 记录失败返回的结果值
this.reason = null
// 数组记录所有的成功回调
this.onFulfilled = []
// 数组记录所有的失败回调
this.onRejected = []
function resolve(value) {
if (that.status === PENGDING) {
that.status = FULFILLED
that.value = value
// 有缓存成功回调,则执行
that.onFulfilled.forEach((fn) => fn(value))
}
}
function reject(reason) {
if (that.status === PENGDING) {
that.status = REJECTED
that.reason = reason
// 有缓存失败回调,则执行
that.REJECTED.forEach((fn) => fn(reason))
}
}
executor(resolve, reject)
}
Promise.prototype.then = function (onFulfilled, onRejected) {
if (this.status === FULFILLED) {
// 调用成功回调
onFulfilled(this.value)
} else if (this.status === REJECTED) {
// 调用失败回调
onRejected(this.reason)
} else {
// 挂载状态下缓存所有的成功和失败回调
this.onFulfilled.push(onFulfilled)
this.onRejected.push(onRejected)
}
}
module.exports = Promise
处理链式调用及值穿透(同步)
Promise的重中之重就是链式调用,主要处理的就是下述三种情况。根据Promise/A+的思想,每次执行完promise.then就创建新的promise,并把上一个then的返回值传递给下个promise的then方法,就可达到链式调用及值穿透的效果。resolve和reject同理。
// main.js
const Promise = require('./promise')
const p = new Promise((resolve, reject) => {
// 目前只处理同步链式调用
resolve('success')
})
// 返回普通值
p.then((value) => {
return value
}).then((value) => {
console.log(value)
})
// 报错 Cannot read property 'then' of undefined
// 返回promise
p.then((value) => {
return new Promise((resolve, reject) => {
resolve(value)
})
}).then((value) => {
console.log(value)
})
// 报错 Cannot read property 'then' of undefined
// 值穿透
p.then().then((value) => {
console.log(value)
})
// 报错 Cannot read property 'then' of undefined
改进代码主要部分如下。
...
Promise.prototype.then = function (onFulfilled, onRejected) {
let that = this
// 值穿透问题
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value
onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason }
const p2 = new Promise((resolve, reject) => {
if (that.status === FULFILLED) {
// 调用成功回调
const x = onFulfilled(that.value)
resolvePromise(x, resolve, reject)
} else if (that.status === REJECTED) {
// 调用失败回调
const x = onRejected(that.reason)
resolvePromise(x, resolve, reject)
} else {
// 挂载状态下缓存所有的成功和失败回调
that.onFulfilled.push(onFulfilled)
that.onRejected.push(onRejected)
}
})
return p2
}
function resolvePromise(x, resolve, reject) {
// 如果 x 是 promise 对象执行其 then 函数(参数为p2的回调函数,而非返回的x的回调函数)
if (x instanceof Promise) {
x.then(
(value) => resolve(value),
(reason) => reject(reason)
)
} else {
// 如果 x 是 普通值
resolve(x)
}
}
module.exports = Promise
继续执行上述三个例子的代码,均可得出success的结果。
- 如果是普通值,直接
resolve返回值x。 - 如果是
Promise,执行x.then。注意!!!then函数传参是p2的resolve和reject,所以执行const x = onFulfilled(that.value)时,执行的是p2的resolve函数,从而将值传递下去。 - 值穿透在没有
resolve和reject参数的前提下,判断入参是否是函数,不是的话,赋值默认函数。
处理链式调用及值穿透(异步)
将main.js内逻辑更改为异步的情况。
// main.js
const Promise = require('./promise')
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
})
})
...
来分析下原因:首先p.then和p.then.then都是第一轮事件循环,所以会于setTimeout去执行。其次,异步的情况下,我们是缓存成功和回调函数,等待resolve或reject的时候再去执行。但我们缓存的仅仅是回调函数,并不是一个promise对象,所以只要再包装一层即可。
// promise.js
Promise.prototype.then = function (onFulfilled, onRejected) {
...
const p2 = new Promise((resolve, reject) => {
if (that.status === FULFILLED) {
...
} else if (that.status === REJECTED) {
...
} else {
// 挂载状态下缓存所有的成功和失败回调
that.onFulfilled.push(() => {
const x = onFulfilled(that.value)
resolvePromise(x, resolve, reject)
})
that.onRejected.push(() => {
const x = onRejected(that.reason)
resolvePromise(x, resolve, reject)
})
}
})
return p2
}
处理返回自身的情况
原生promise会将与返回与自身相等的错误情况抛出Chaining cycle detected for promise #<Promise>。
const p = new Promise((resolve, reject) => {
resolve('success')
})
const p1 = p.then((value) => {
console.log(value)
return p1
})
resolvePromise增加p2传参,并添加判断
Promise.prototype.then = function (onFulfilled, onRejected) {
let that = this
// 值穿透问题
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) => value
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
const p2 = new Promise((resolve, reject) => {
if (that.status === FULFILLED) {
// 调用成功回调
const x = onFulfilled(that.value)
resolvePromise(p2, x, resolve, reject)
} else if (that.status === REJECTED) {
// 调用失败回调
const x = onRejected(that.reason)
resolvePromise(p2, x, resolve, reject)
} else {
// 挂载状态下缓存所有的成功和失败回调
that.onFulfilled.push(() => {
const x = onFulfilled(that.value)
resolvePromise(p2, x, resolve, reject)
})
that.onRejected.push(() => {
const x = onRejected(that.reason)
resolvePromise(p2, x, resolve, reject)
})
}
})
return p2
}
function resolvePromise(p2, x, resolve, reject) {
// 如果返回自身抛出错误
if (x === p2)
reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
// 如果 x 是 promise 对象
if (x instanceof Promise) {
x.then(
(value) => resolve(value),
(reason) => reject(reason)
)
} else {
// 如果 x 是 普通值
resolve(x)
}
}
执行报错p1 is not defined,查看堆栈报错由于resolvePromise(p2, x, resolve, reject)中的p2未初始化。
根据
promise/A+规范提示,可巧妙利用宏任务/微任务去解决这个问题,这边我选择setTimeout宏任务的形式。
更改代码如下:
Promise.prototype.then = function (onFulfilled, onRejected) {
let that = this
// 值穿透问题
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) => value
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
const p2 = new Promise((resolve, reject) => {
if (that.status === FULFILLED) {
// 调用成功回调
setTimeout(() => {
const x = onFulfilled(that.value)
resolvePromise(p2, x, resolve, reject)
})
} else if (that.status === REJECTED) {
// 调用失败回调
setTimeout(() => {
const x = onRejected(that.reason)
resolvePromise(p2, x, resolve, reject)
})
} else {
// 挂载状态下缓存所有的成功和失败回调
that.onFulfilled.push(() => {
setTimeout(() => {
const x = onFulfilled(that.value)
resolvePromise(p2, x, resolve, reject)
})
})
that.onRejected.push(() => {
setTimeout(() => {
const x = onRejected(that.reason)
resolvePromise(p2, x, resolve, reject)
})
})
}
})
return p2
}
增加错误处理
主要捕获构造函数及then函数的错误。用try/catch形式捕获
const PENGDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
let that = this
// 初始状态为pengding
this.status = PENGDING
// 记录成功返回的结果值
this.value = null
// 记录失败返回的结果值
this.reason = null
// 数组记录所有的成功回调
this.onFulfilled = []
// 数组记录所有的失败回调
this.onRejected = []
function resolve(value) {
if (that.status === PENGDING) {
that.status = FULFILLED
that.value = value
// 有缓存成功回调,则执行
that.onFulfilled.forEach((fn) => fn(value))
}
}
function reject(reason) {
if (that.status === PENGDING) {
that.status = REJECTED
that.reason = reason
// 有缓存失败回调,则执行
that.onRejected.forEach((fn) => fn(reason))
}
}
// 构造函数捕获错误
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
let that = this
// 值穿透问题
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) => value
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
const p2 = new Promise((resolve, reject) => {
if (that.status === FULFILLED) {
// 调用成功回调
setTimeout(() => {
try {
const x = onFulfilled(that.value)
resolvePromise(p2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
} else if (that.status === REJECTED) {
// 调用失败回调
setTimeout(() => {
try {
const x = onRejected(that.reason)
resolvePromise(p2, x, resolve, reject)
} catch (err) {
reject(err)
}
} else {
// 挂载状态下缓存所有的成功和失败回调
that.onFulfilled.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(that.value)
resolvePromise(p2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
that.onRejected.push(() => {
setTimeout(() => {
try {
const x = onRejected(that.reason)
resolvePromise(p2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
}
})
return p2
}
function resolvePromise(p2, x, resolve, reject) {
// 如果返回自身抛出错误
if (x === p2)
reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
// 如果 x 是 promise 对象
if (x instanceof Promise) {
x.then(
(value) => resolve(value),
(reason) => reject(reason)
)
} else {
// 如果 x 是 普通值
resolve(x)
}
}
module.exports = Promise
拿个🌰验证
//main.js
const Promise = require('./promise')
const p = new Promise((resolve, reject) => {
throw new Error('failed')
})
p.then(
(value) => {
console.log(value)
return p1
},
(reason) => {
console.log(reason)
}
)
// Error: failed
标准版Promise
丐版Promise已经基本满足所有情况,但是总想能有个证书,那还得符合我们Promise/A+规范。
npm initnpm install promises-aplus-tests --save-dev- 添加包的方法
Promise.deferred = function () {
var result = {}
result.promise = new Promise(function (resolve, reject) {
result.resolve = resolve
result.reject = reject
})
return result
}
package.json的scripts字段更改命令为test: promises-aplus-tests promise,并执行npm run test。
执行,不出所料,一堆报错。根据提示需按照规范需更改resolvePromise函数
function resolvePromise(p2, x, resolve, reject) {
// 如果返回自身抛出错误
if (p2 === x) {
reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// 总的来说,就是执行x.then()
if ((x && typeof x === 'object') || typeof x === 'function') {
// 避免同个回调被多次调用
let called = false
try {
let then = x.then
if (typeof then === 'function') {
then.call(
x,
(y) => {
if (called) return
called = true
resolvePromise(p2, y, resolve, reject)
},
(r) => {
if (called) return
called = true
reject(r)
}
)
} else {
// 不是函数 resolve(x)
if (called) return
called = true
resolve(x)
}
} catch (err) {
// 上述代码块报错 reject(err)
if (called) return
called = true
reject(err)
}
} else {
// x 不是对象或函数的情况下(包含x为null的情况)
resolve(x)
}
}
至此,标准版Promise更改完毕。
Promise周边
Promise.resolve
Promise.resolve(value)
返回一个给定值解析后的promise对象。
- 如果当前值是
promise对象,返回这个promise。 - 如果是个带有
then函数的对象,采用它的最终状态。 - 返回当前值作为完成状态的
promise。
手写实现
Promise.resolve = function (param) {
if (param instanceof Promise) {
return param
}
return new Promise((resolve, reject) => {
if (
param &&
typeof param === 'object' &&
typeof param.then === 'function'
) {
param.then(resolve, reject)
} else {
resolve(param)
}
})
}
Promise.reject
返回一个带有拒绝原因的Promise对象。
基本实现
Promise.reject = function(reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
Promise.all
Promise.all(iterable)
Promise.all接收一个promise的iterable类型(Array、Map、Set)的输入,并且只返回一个promise实例。该实例的resolve是在所有的promise的resolve回调结束后执行。reject是在任一个promise执行reject后就会立即抛出错误。
基本使用
const p1 = 'promise'
const p2 = new Promise((resolve, reject) => {
resolve('success')
})
Promise.all([p1, p2].then((values) => {
console.log(values) // ['promise', 'success']
}))
手写实现
Promise.all = function (params) {
const promises = Array.from(params)
let values = []
return new Promise((resolve, reject) => {
if (!params.length) resolve(values)
promises.forEach((promise, index) => {
// 所有执行完 返回values
if (index === promises.length - 1) resolve(values)
Promise.resolve(promise).then(
(value) => {
console.log(value)
values[index] = value
},
// 任意一个错误,reject
(reason) => {
reject(reason)
}
)
})
})
}
Promise.race
返回一个promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
基本使用
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1, success')
}, 100)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2, success')
}, 200)
})
Promise.race([p1, p2]).then((value) => {
console.log(value)
})
手写实现
Promise.race = function (params) {
const promises = Array.from(params)
return new Promise((resolve, reject) => {
if (!promises.length) return
promises.forEach((promise) => {
Promise.resolve(promise).then(
(value) => {
resolve(value)
return
},
(reason) => {
reject(reason)
return
}
)
})
})
}
扩展
超时控制
利用 Promise.race 的特性去完成超时控制。
function sleep(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('timeout')), delay)
})
}
function isTimeout(p, delay) {
return Promise.race([p, sleep(delay)])
}
// 模拟异步请求
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("done")
}, 1000)
})
isTimeout(promise, 500).then(value => {
// 未超时
console.log(value)
}).catch(error => {
// 超时处理
console.log(error)
})
限制并发请求数
通过 Promise.all 的特性去并发请求,用变量 max 去限制请求数量。
const urls = [1,2,3,4,5,6,7,8,9]
const limit = 4
function promiseFactory(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('当前请求的url', url)
resolve(url)
})
})
.finally(() => {
if(urls.length) return promiseFactory(urls.shift())
})
}
function controlComplicatingRequest(urls, limit) {
let promises = []
while(limit--) {
promises.push(promiseFactory(urls.shift()))
}
return Promise.all(promises)
}
controlComplicatingRequest(urls, limit).then(() => {
console.log('all resolved')
})
总结
如果觉得有帮助的,毫不吝啬地点个💕呗。
下一章,会抠一抠事件循环,有兴趣地加个关注,一起学习。👀