手写Promise
Promise规范
https://promisesaplus.com/ PromiseA+规范
A+规范只有then方法
Promise 类设计
两种方式,我选第一种
构造函数constructor设计
实现:
- 让executor函数执行起来
- 定义状态常量
- 定义resolve,reject回调,执行的时候改变状态
const STATUE_PENDING = 'pending' const STATUE_FULFILLED = 'fulfilled' const STATUS_REJECTED = 'rejected'
class MyPromise { constructor(executor) { this.status = STATUE_PENDING this.value = undefined this.reason = undefined const resolve = (value) => { this.status = STATUE_FULFILLED this.value = value console.log("resolve执行", value); } const reject = (reason) => { this.status = STATUS_REJECTED this.reason = reason console.log("reject执行", reason); }
executor(resolve, reject) } }
const promise = new MyPromise((resolve, reject) => { console.log("pending~"); resolve("111") reject("222") })
|
- 为了不让resolve,reject回调可以同时执行,我们需要加一个判断,只有状态为pending的时候才执行回调
const resolve = (value) => { if (this.status === STATUE_PENDING) { this.status = STATUE_FULFILLED this.value = value console.log("resolve执行", value); } }
const reject = (reason) => { if (this.status === STATUE_PENDING) { this.status = STATUS_REJECTED this.reason = reason console.log("reject执行", reason); } }
|
then 方法的实现
简单实现
- then方法接收两个参数,分别是成功时的回调onFulfilled,失败时的回调onRejected
- 把回调保存到当前的promise实例,并在对应的resolve,reject回调时执行
class MyPromise { constructor(executor) { this.status = STATUE_PENDING this.value = undefined this.reason = undefined
const resolve = (value) => { if (this.status === STATUE_PENDING) { this.status = STATUE_FULFILLED this.value = value console.log("resolve执行", value); this.onFulfilled(this.value) } }
const reject = (reason) => { if (this.status === STATUE_PENDING) { this.status = STATUS_REJECTED this.reason = reason console.log("reject执行", reason); this.onRejected(this.reason) } }
executor(resolve, reject) }
then(onFulfilled, onRejected) { this.onFulfilled = onFulfilled this.onRejected = onRejected } }
const promise = new MyPromise((resolve, reject) => { console.log("pending~"); resolve("111") reject("222") })
promise.then(res => { console.log("res: ", res); }, err => { console.log("err: ", err); })
|
这时候我们会发现,报错了:this.onFulfilled is not a function
为什么??
我们捋一捋代码的执行过程就可以发现,当执行resolve("111")
的时候,都还没有到then那一步,也就是说还没执行then方法,那么怎么可能会有 onFulfilled, onRejected
方法呢!
这时候我们就用到了一个方法:queueMicrotask()
MDN:https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask
简单来说,这个方法可以把我们传入的方法加入到微任务队列,让它延迟到当前主线程的任务执行完之后再执行
所以可以在resolve方法执行的时候,先不执行onFulfilled,把它加入到微任务队列,接着就会继续执行下面的then方法,这时候就有onFulfilled, onRejected
方法了,最后主线程任务执行完,再执行微任务队列中的 onFulfilled,,(reject同理)
const resolve = (value) => { if (this.status === STATUE_PENDING) { queueMicrotask(() => { this.status = STATUE_FULFILLED this.value = value this.onFulfilled(this.value) }) } }
const reject = (reason) => { if (this.status === STATUE_PENDING) { queueMicrotask(() => { this.status = STATUS_REJECTED this.reason = reason this.onRejected(this.reason) }) } }
|
但是这个时候又出现了一个问题,resolve和reject可以同时调用了
因为我们执行resolve的实现,加入到微任务队列,因此状态没有被改变,依然是pending
接着执行reject,判断状态是pending,所以也加入到微任务队列了
解决:在执行微任务的代码一开始加入一个判断,如果状态已经不是pending了,直接return出去,停止执行
const resolve = (value) => { if (this.status === STATUE_PENDING) { queueMicrotask(() => { if (this.status !== STATUE_PENDING) return this.status = STATUE_FULFILLED this.value = value this.onFulfilled(this.value) }) } }
const reject = (reason) => { if (this.status === STATUE_PENDING) { queueMicrotask(() => { if (this.status !== STATUE_PENDING) return this.status = STATUS_REJECTED this.reason = reason this.onRejected(this.reason) }) } }
|
当然,有人可能会说把状态改变放到微任务外面不就行了吗?
不行!!这样在后面实现链式调用会出现问题
你后面可以试一下,多个then执行和链式调用一起的时候,执行顺序是有问题的
至于为什么,那就自己捋一捋整个代码的执行顺序叭
同时调用多个then的实现
上面的实现是不能同时调用多个then的
promise.then(res => { console.log("res: ", res); }, err => { console.log("err: ", err); })
promise.then(res => { console.log("res2: ", res); }, err => { console.log("err2: ", err); })
|
后面的then会覆盖前面的,因此只会执行 res2 或者 err2
实现思路:我们需要把then传入的回调保存到一个数组,到时候一起执行
class MyPromise { constructor(executor) { this.status = STATUE_PENDING this.value = undefined this.reason = undefined this.onFulfilledCallbacks = [] this.onRejectedCallbacks = []
const resolve = (value) => { if (this.status === STATUE_PENDING) { queueMicrotask(() => { if (this.status !== STATUE_PENDING) return this.status = STATUE_FULFILLED this.value = value this.onFulfilledCallbacks.forEach(fn => { fn(this.value) }) }) } }
const reject = (reason) => { if (this.status === STATUE_PENDING) { queueMicrotask(() => { if (this.status !== STATUE_PENDING) return this.status = STATUS_REJECTED this.reason = reason this.onRejectedCallbacks.forEach(fn => { fn(this.reason) }) }) } }
executor(resolve, reject) }
then(onFulfilled, onRejected) { this.onFulfilledCallbacks.push(onFulfilled) this.onRejectedCallbacks.push(onRejected) } }
const promise = new MyPromise((resolve, reject) => { console.log("pending~"); resolve("111") reject("222") })
promise.then(res => { console.log("res: ", res); }, err => { console.log("err: ", err); })
promise.then(res => { console.log("res2: ", res); }, err => { console.log("err2: ", err); })
|
延迟调用then
还有一种情况:resolve或reject执行完之后,延迟执行then
const promise = new MyPromise((resolve, reject) => { console.log("pending~"); resolve("111") })
setTimeout(() => { promise.then(res => { console.log("res3: ", res); }, err => { console.log("err3: ", err); }) }, 1000)
|
setTimeout 会在主线程的任务执行完,并且微任务队列也执行完后才执行
捋一捋执行顺序:
首先主线程 resolve("111")
执行,先改变状态为fulfilled,保存value,然后在微任务队列中加入一个任务
然后继续往下,setTimeout 被加入到宏任务队列,并未执行then
主线程任务执行完,执行微任务队列中的任务,遍历数组中的函数并执行,但我们可以看到,前面then没有执行,数组中并没有对应回调,所以没有执行
接着执行宏任务队列中setTimeout,也就是then方法被执行,
这个时候才把回调加入数组。。。任务结束
因此可以想到实现思路:在then方法中加入判断,如果当前状态是确定的,(fulfilled或rejected),那么直接执行这个回调,不用添加到数组
then(onFulfilled, onRejected) { if (this.status === STATUE_FULFILLED && onFulfilled) { onFulfilled(this.value) } if (this.status === STATUS_REJECTED && onRejected) { onRejected(this.reason) }
if (this.status === STATUE_PENDING) { this.onFulfilledCallbacks.push(onFulfilled) this.onRejectedCallbacks.push(onRejected) } }
|
链式调用then
then需要返回一个promise,后面才能继续调用then
并且下一次的then是在上次回调执行完之后才执行的,而且需要拿到上次onFulfilled/onRejected 执行完之后的结果,再去执行resolve,reject
then(onFulfilled, onRejected) { if (this.status === STATUE_FULFILLED && onFulfilled) { onFulfilled(this.value) } if (this.status === STATUS_REJECTED && onRejected) { onRejected(this.reason) }
if (this.status === STATUE_PENDING) { this.onFulfilledCallbacks.push(onFulfilled) this.onRejectedCallbacks.push(onRejected) }
return new MyPromise((resolve, reject) => { resolve() reject() }) }
|
现在有个问题,就是怎么拿到上次执行的结果值?
思路:把上面那一坨代码放到new里面执行,这样就让里边具备了拿到返回结果的能力
then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { if (this.status === STATUE_FULFILLED && onFulfilled) { const value = onFulfilled(this.value) resolve(value) } if (this.status === STATUS_REJECTED && onRejected) { const reason = onRejected(this.reason) resolve(reason) }
if (this.status === STATUE_PENDING) { this.onFulfilledCallbacks.push(onFulfilled) this.onRejectedCallbacks.push(onRejected) } }) }
|
因为即使我们不链式调用,里面的代码还是会执行的,我们只是想拿到想要的结果,并且在某个时机调用 resolve, reject
两种情况都调用resolve?那什么时候调用reject呢?
我们知道,只有在上次的then中抛出异常了,下次才会执行reject
所以我们再改进一下:使用try…catch
then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { if (this.status === STATUE_FULFILLED && onFulfilled) { try { const value = onFulfilled(this.value) resolve(value) } catch (err) { reject(err) } } if (this.status === STATUS_REJECTED && onRejected) { try { const reason = onRejected(this.reason) resolve(reason) } catch (err) { reject(err) } }
if (this.status === STATUE_PENDING) { this.onFulfilledCallbacks.push(onFulfilled) this.onRejectedCallbacks.push(onRejected) } }) }
|
上面都是在状态已经确定之后的情况,我们知道在状态为pending的时候,我们是把回调保存到数组,然后在上面的resolve、reject中遍历数组执行的
this.onFulfilledCallbacks.forEach(fn => { let value = fn(this.value) })
|
那这个执行结果怎么拿到呢然后传给下面的resolve、reject呢?
解决方案:改进我们添加到数组的方式
if (this.status === STATUE_PENDING) { this.onFulfilledCallbacks.push(() => { onFulfilled() }) this.onRejectedCallbacks.push(() => { onRejected() }) }
|
我们不直接把回调添加到数组,而是使用一个函数包裹起来,那么到时上面遍历执行的就是这个包裹的函数,然后我们在这里内部执行onFulfilled,就可以在这里拿到onFulfilled的执行结果
if (this.status === STATUE_PENDING) { this.onFulfilledCallbacks.push(() => { const value = onFulfilled(this.value) resolve(value) }) this.onRejectedCallbacks.push(() => { const reason = onRejected(this.reason) resolve(reason) }) }
|
同理,try…catch改进一下
if (this.status === STATUE_PENDING) { this.onFulfilledCallbacks.push(() => { try { const value = onFulfilled(this.value) resolve(value) } catch (err) { reject(err) } }) this.onRejectedCallbacks.push(() => { try { const reason = onRejected(this.reason) resolve(reason) } catch (err) { reject(err) } }) }
|
测试的时候发现,有一种情况我们没有处理
const promise = new MyPromise((resolve, reject) => { console.log("pending~"); throw new Error("error message") })
|
改进,在 executor 那里加try…catch,如果抛出了异常直接执行reject
try { executor(resolve, reject) } catch (err) { reject(err) }
|
测试:
const promise = new MyPromise((resolve, reject) => { console.log("pending~"); throw new Error("error message") })
promise.then(res => { console.log("res: ", res); return "aaa" }, err => { console.log("err: ", err); return "bbb" }).then(res => { console.log("res2: ", res); }, err => { console.log("err2: ", err); })
|
测试没问题!!
工具函数
到这里then方法基本就实现完了,但是我们发现很多 try…catch的重复代码
我们可以做一个抽取,定义一个工具函数
function execFunctionWithCatchError(exeFn, value, resolve, reject) { try { const result = exeFn(value) resolve(result) } catch (err) { reject(err) } }
|
然后优化then方法:
then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { if (this.status === STATUE_FULFILLED && onFulfilled) { execFunctionWithCatchError(onFulfilled, this.value, resolve, reject) } if (this.status === STATUS_REJECTED && onRejected) { execFunctionWithCatchError(onRejected, this.reason, resolve, reject) }
if (this.status === STATUE_PENDING) { this.onFulfilledCallbacks.push(() => { execFunctionWithCatchError(onFulfilled, this.value, resolve, reject) }) this.onRejectedCallbacks.push(() => { execFunctionWithCatchError(onRejected, this.reason, resolve, reject) }) } }) }
|
catch 方法的实现
catch接收一个参数,也就是失败的回调,因为catch实际上就相当于语法糖,而且也是返回一个新的promise,所以我们可以直接调用then
catch(onRejected) { return this.then(undefined, onRejected) }
|
测试:
const promise = new MyPromise((resolve, reject) => { console.log("pending~"); reject(222) })
promise.then(res => { console.log("res: ", res); return "aaa" }).catch(err => { console.log("catch", err); })
|
额,报错了
别急,分析原因。
我们可以观察一下,本来我们的失败回调应该是在第一个then中传入的,是作为第一个promise的失败的回调的
promise.then(res => { console.log("res: ", res); return "aaa" }, err => { console.log("catch", err); }).catch()
|
但是现在我们写到catch里面,意味着这个catch传入的回调是被加入到一个新的promise中,作为新的promise的失败的回调了,所以执行不到这个回调了
promise1 -> undefined
promise2 -> err ={}
那么我们怎么样让promise1执行到err ={} 呢??
实现思路:假如说promise1的失败回调没有值,我们直接抛出去,这样就会来到promise2中失败的回调了
promise1 -> err => {throw err} // 抛出去
promise2 -> err ={} // 就会执行到这里
还有一个地方是,既然onFulfilled或onRejected可能会传进来undefined,那么我们最好加上一个判断
then(onFulfilled, onRejected) { const defaultOnRejected = err => { throw err } onRejected = onRejected || defaultOnRejected
return new MyPromise((resolve, reject) => { if (this.status === STATUE_FULFILLED && onFulfilled) { execFunctionWithCatchError(onFulfilled, this.value, resolve, reject) } if (this.status === STATUS_REJECTED && onRejected) { execFunctionWithCatchError(onRejected, this.reason, resolve, reject) }
if (this.status === STATUE_PENDING) { if (onFulfilled) this.onFulfilledCallbacks.push(() => { execFunctionWithCatchError(onFulfilled, this.value, resolve, reject) }) if (onRejected) this.onRejectedCallbacks.push(() => { execFunctionWithCatchError(onRejected, this.reason, resolve, reject) }) } }) }
|
finally 方法的实现
接收一个参数,无论成功或失败,最后都会执行
也就是说,成功与失败的回调都是你传入的那个函数
finally(onFinally) { return this.then(() => { onFinally() }, () => { onFinally() }) }
|
但是还会有一个问题
const promise = new MyPromise((resolve, reject) => { console.log("pending~"); resolve(111) })
promise.then(res => { console.log("res: ", res); }).catch(err => { console.log("catch", err); }).finally(() => { console.log('finally'); })
|
分析:捋一捋
如果上面是resolve的话,那么就会执行圈1,然后来到promise2的成功回调
promise1:成功回调是 圈1,失败回调是我们默认的reject
promise2:没有成功回调,失败回调是圈2,
没有成功回调,这时候就会出错了,后面也不会继续执行了,因此要给默认的成功回调 value => { return value }
然后才会执行finally的第一个回调
then(onFulfilled, onRejected) { const defaultOnRejected = err => { throw err } onRejected = onRejected || defaultOnRejected
const defaultOnFulfilled = value => { return value } onFulfilled = onFulfilled || defaultOnFulfilled return new MyPromise((resolve, reject) => { if (this.status === STATUE_FULFILLED) { execFunctionWithCatchError(onFulfilled, this.value, resolve, reject) } if (this.status === STATUS_REJECTED) { execFunctionWithCatchError(onRejected, this.reason, resolve, reject) }
if (this.status === STATUE_PENDING) { if (onFulfilled) this.onFulfilledCallbacks.push(() => { execFunctionWithCatchError(onFulfilled, this.value, resolve, reject) }) if (onRejected) this.onRejectedCallbacks.push(() => { execFunctionWithCatchError(onRejected, this.reason, resolve, reject) }) } }) }
|
resolve reject 类方法的实现
resolve 类方法本质就是new了一个 promise,然后直接调用resolve
reject同理
static resolve(value) { return new MyPromise((resolve) => resolve(value)) }
static reject(reason) { return new MyPromise((resolve, reject) => reject(reason)) }
|
测试:
MyPromise.resolve(123).then(res => { console.log(res); })
|
all 方法的实现
all方法接收一个promise数组,同样返回一个promise
关键思路:什么时候要执行resolve, 什么时候要执行reject
static all(promises) { return new MyPromise((resolve, reject) => { const values = [] promises.forEach(promise => { promise.then(res => { values.push(res)
if (values.length === promises.length) { resolve(values) } }, err => { reject(err) }) }) }) }
|
测试:
const p1 = new MyPromise((resolve) => { setTimeout(() => { resolve("111") }, 1000); }) const p2 = new MyPromise((resolve, reject) => { setTimeout(() => { resolve("222") }, 2000); })
const p3 = new MyPromise((resolve) => { setTimeout(() => { resolve("333") }, 3000); })
MyPromise.all([p1, p2, p3]).then(res => { console.log(res); }, err => { console.log(err); })
|
allSettled 方法的实现
与all类型,区别就是无论成功与否都会把结果保存到数组
测试:
const p1 = new MyPromise((resolve) => { setTimeout(() => { resolve("111") }, 1000); }) const p2 = new MyPromise((resolve, reject) => { setTimeout(() => { reject("222") }, 2000); })
const p3 = new MyPromise((resolve) => { setTimeout(() => { resolve("333") }, 3000); })
MyPromise.allSettled([p1, p2, p3]).then(res => { console.log(res); })
|
race 方法的实现
只要状态改变就执行回调
static race(promises) { return new MyPromise((resolve, reject) => { promises.forEach(promise => { promise.then(resolve, reject) }) }) }
|
any 方法的实现
等到第一个fulfilled状态,才执行resolve回调
如果全都是rejected状态,那么抛出异常
static any(promises) { const reasons = [] return new MyPromise((resolve, reject) => { promises.forEach(promise => { promise.then(res => { resolve(res) }, err => { reasons.push(err) if (reasons.length === promises.length) { reject(new AggregateError(reasons)) } }) }) }) }
|
测试全都是reject的情况
const p1 = new MyPromise((resolve, reject) => { setTimeout(() => { reject("111") }, 1000); }) const p2 = new MyPromise((resolve, reject) => { setTimeout(() => { reject("222") }, 2000); })
const p3 = new MyPromise((resolve, reject) => { setTimeout(() => { reject("333") }, 3000); })
MyPromise.any([p1, p2, p3]).then(res => { console.log(res); }, err => { console.log(err, err.errors); })
|
到此,手写promise就差不多大功告成啦!