字节前端实习一面就让我手写 Promise,没写出直接挂了 💔,亡羊补牢,为时不晚。
使用方法回顾
作为 ES6 提出来的新语法,首先回顾一下 Promise 的使用方法。
1 2 3 4 5 6 7
| const promise = new Promise((reslove, reject) => { reslove(233) })
promise.then((value) => { console.log(value) })
|
简单来说就是实例化一个 Promise
对象,实例化的时候传一个函数进去,这个函数有两个变量:
resolve
,是一个回调函数,用于处理成功的数据reject
,也是一个回调函数,用于处理失败的信息
简易版 Promise
简易版 Promise 🍣 呈上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| const PENDING = 'pending' const FULLFILLED = 'resolved' const REJECTED = 'rejected'
class MyPromise { constructor(executor) { executor(this.resolve, this.reject) }
status = PENDING
value = null reason = null
resolve = (value) => { if (this.status === PENDING) { this.status = FULLFILLED this.value = value } }
reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason } }
then(onFullfilled, onRejected) { if (this.status === FULLFILLED) { onFullfilled(this.value) } else if (this.status === REJECTED) { onRejected(this.reason) } } }
|
测试成功:
1 2 3 4 5 6 7
| const promise = new MyPromise((reslove, rejectct) => { reslove(233) })
promise.then((value) => { console.log(value) })
|
然而这样子的代码是无法实现异步操作的,因为我们的 then
方法是在主线程中直接运行的,不会受到 executor
(也就是新建 Promise 对象的时候传的函数)里异步操作的限制。
异步操作扩展
因此,为了允许异步操作,我们要进行以下扩展:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| onFulfilledCallback = null
onRejectedCallback = null
resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED this.value = value this.onFulfilledCallback && this.onFulfilledCallback(value) } }
reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason this.onRejectedCallback && this.onRejectedCallback(reason) } }
then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } else if (this.status === REJECTED) { onRejected(this.reason) } else if (this.status === PENDING) { this.onFulfilledCallback = onFulfilled this.onRejectedCallback = onRejected } }
|
但是这个方式有个很明显的缺陷,就是我们的 promise
一旦有多个 then
,它的原先的回调函数就会被新的回调函数覆盖掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const promise = new MyPromise((resolve, reject) => { setTimeout(() => { resolve('success') }, 1000) })
promise.then((value) => { console.log(1) console.log(value) })
promise.then((value) => { console.log(2) console.log(value) })
promise.then((value) => { console.log(3) console.log(value) })
|
很容易就能想到,用数组来存储所有的成功回调和失败回调函数即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| onFulfilledCallback = []
onRejectedCallback = []
resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED this.value = value while (this.onFulfilledCallback.length) { this.onFulfilledCallback.shift()(value) } } }
reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason while (this.onRejectedCallback.length) { this.onRejectedCallback.shift()(value) } } }
then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } else if (this.status === REJECTED) { onRejected(this.reason) } else if (this.status === PENDING) { this.onFulfilledCallback.push(onFulfilled) this.onRejectedCallback.pop(onRejected) } }
|
链式调用
我们一鼓作气,再把 then
的链式调用给实现了。
逻辑也很清晰,就是在调用 then
的时候,我们需要判断一下,执行成功回调函数的返回值是否是一个 MyPromise
对象,两种情况:
- 是
MyPromise
对象,就继续调用返回的这个对象的 then
方法 - 是普通值,就直接调用成功的回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { const ret = onFulfilled(this.value) if (ret instanceof MyPromise) { ret.then(resolve, reject) } else { resolve(ret) } } else if (this.status === REJECTED) { onRejected(this.reason) } else if (this.status === PENDING) { this.onFulfilledCallback.push(onFulfilled) this.onRejectedCallback.pop(onRejected) } }) }
|
测试结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| promise .then((value) => { console.log(1) console.log(value) return new MyPromise((resolve) => resolve('success')) }) .then((value) => { console.log(2) console.log(value) return new MyPromise((resolve) => resolve('success')) }) .then((value) => { console.log(3) console.log(value) })
|
但是,这个实现还有很多限制,我们逐步解决。
循环调用
按我们上面的写法,我们如果在 promise.then
里面返回了同一个 promise
,结果会报 Uncaught ReferenceError
的错误。
1 2 3 4 5 6 7 8
| const promise = new MyPromise((resolve, reject) => { resolve('success') })
const p = promise.then((value) => { console.log(value) return p })
|
但是,官方的 Promise 报的却是 Type Error
的错误。
1 2 3 4 5 6 7 8
| const promise = new Promise((resolve, reject) => { resolve('success') })
const p = promise.then((value) => { console.log(value) return p })
|
该怎么还原呢,我们尝试修改 then
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| then(onFulfilled, onRejected) { const p = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { queueMicrotask(() => { try { const ret = onFulfilled(this.value) if (p === ret) { return reject( new TypeError('Chaining cycle detected for promise #<Promise>') ) } if (ret instanceof MyPromise) { ret.then(resolve, reject) } else { resolve(ret) } } catch (error) { reject(error) } }) } else if (this.status === REJECTED) { onRejected(this.reason) } else if (this.status === PENDING) { this.onFulfilledCallback.push(onFulfilled) this.onRejectedCallback.pop(onRejected) } })
return p }
|
在这里,还记得之前为了实现链式调用而新建了 MyPromise
对象,当时是直接 return
了这个对象,现在我们对其命名为 p
,并且保存下来,以便后续判断循环调用。
只需要在获取成功回调返回值的时候,判断其是否和我们新建的 p
相同
由于 p
是 New
完之后才能获得的一个对象,所以直接在原本的 then
函数中增加判重逻辑即可。
验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const promise = new MyPromise((resolve, reject) => { resolve('new success') })
const p = promise.then((value) => { console.log(1) console.log(value) return p })
p.then( (value) => { console.log(2) console.log(value) }, (reason) => { console.log(3) console.log(reason) } )
|
捕获错误
在 Promise 中,为了避免发生错误就中断代码运行,我们需要对两个地方的错误进行捕获。
在实例化 MyPromise
的时候
在 then
的过程中
针对实例化的错误肯定是在构造函数中进行捕捉,实际上只需要对 executor
执行的增加异常捕获操作。
1 2 3 4 5 6 7 8 9
| constructor(executor) { try { executor(this.resolve, this.reject) } catch (error) { this.reject(error) } }
|
针对 then
方法中出现的错误,显然就是处理对其进行异常捕获啦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ... queueMicrotask(() => { try { const ret = onFulfilled(this.value) if (p === ret) { return reject( new TypeError('Chaining cycle detected for promise #<Promise>') ) } if (ret instanceof MyPromise) { ret.then(resolve, reject) } else { resolve(ret) } } catch (error) { reject(error) } }) ...
|
让我们来看看效果是否和我们预期一样。
构造函数中捕获错误:
1 2 3 4
| const promise = new MyPromise((resolve, reject) => { throw new Error('new failed') resolve('success') })
|
then
中捕获错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const promise = new MyPromise((resolve, reject) => { resolve('new success') })
promise .then( (value) => { console.log(1, value) throw new Error('then failed') }, (reason) => { console.log(2, reason) } ) .then( (value) => { console.log(3, value) }, (reason) => { console.log(4, reason) } )
|
优化 then 中的 REJECTED 和 PENDING 状态
先前我们对 then
方法中 FULFLLED
状态的操作进行了优化,包括:
- 创建微任务,实现检测循环调用错误
- 执行回调时增加捕获异常功能
现在我们也对 REJECTED
和 PENDING
状态进行以上补充。
首先为了书写方便,先定义一个处理 promise 返回值的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function resolvePromise(p, ret, resolve, reject) { if (p === ret) { return reject( new TypeError('Chaining cycle detected for promise #<Promise>') ) } if (ret instanceof MyPromise) { ret.then(resolve, reject) } else { resolve(ret) } }
|
修改 then
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| then(onFulfilled, onRejected) { const p = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { queueMicrotask(() => { try { const ret = onFulfilled(this.value) resolvePromise(p, ret, resolve, reject) } catch (error) { reject(error) } }) } else if (this.status === REJECTED) { queueMicrotask(() => { try { const ret = onRejected(this.reason) resolvePromise(p, ret, resolve, reject) } catch (error) { reject(error) } }) } else if (this.status === PENDING) { this.onFulfilledCallback.push(() => { queueMicrotask(() => { try { const ret = onFulfilled(this.value) resolvePromise(p, ret, resolve, reject) } catch (error) { reject(error) } }) }) this.onRejectedCallback.push(() => { queueMicrotask(() => { try { const ret = onRejected(this.value) resolvePromise(p, ret, resolve, reject) } catch (error) { reject(error) } }) }) } })
return p }
|
then 方法中的默认函数
在调用 then
方法的时候,应该允许不写成功和失败的回调。
1 2 3 4 5 6 7 8 9 10 11 12
| then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason } ... }
|
resolve 和 reject 的静态调用
原生的 Promise 是支持无需实例化,直接调用 Promise.resolve()
来返回一个 Promise
对象的。
如果我们尝试用自定义的 MyPromise
进行这种操作:
1 2 3 4 5 6 7 8
| MyPromise.resolve() .then(() => { console.log('1') return MyPromise.resolve('success') }) .then((value) => { console.log(2, value) })
|
报错提示如下:
因为是需要调用类方法,所以我们需要用 static
关键字声明 resolve
和 reject
的静态方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class MyPromise { ... static resolve(parameter) { if (parameter instanceof MyPromise) { return parameter } return new MyPromise((resolve) => { resolve(parameter) }) }
static reject(reason) { return new MyPromise((resolve, reject) => { reject(reason) }) } }
|
测试结果:
代码优化
对声明类时的冗余代码进行优化,优化完代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
| const PENDING = 'pending' const FULFILLED = 'resolved' const REJECTED = 'rejected'
class MyPromise { constructor(executor) { try { executor(this.resolve, this.reject) } catch (error) { this.reject(error) } }
status = PENDING
value = null reason = null
onFulfilledCallback = [] onRejectedCallback = []
resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED this.value = value while (this.onFulfilledCallback.length) { this.onFulfilledCallback.shift()(value) } } }
reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason while (this.onRejectedCallback.length) { this.onRejectedCallback.shift()(reason) } } }
then(onFulfilled, onRejected) { const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value const realOnRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason } const p = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { queueMicrotask(() => { try { const x = realOnFulfilled(this.value) resolvePromise(p, x, resolve, reject) } catch (error) { reject(error) } }) } else if (this.status === REJECTED) { queueMicrotask(() => { try { const x = (realo = OnRejected(this.reason)) resolvePromise(p, x, resolve, reject) } catch (error) { reject(error) } }) } else if (this.status === PENDING) { this.onFulfilledCallback.push(() => { queueMicrotask(() => { try { const x = onFulfilled(this.value) resolvePromise(p, x, resolve, reject) } catch (error) { reject(error) } }) }) this.onRejectedCallback.push(() => { queueMicrotask(() => { try { const ret = onRejected(this.value) resolvePromise(p, ret, resolve, reject) } catch (error) { reject(error) } }) }) } })
return p }
static resolve(parameter) { if (parameter instanceof MyPromise) { return parameter } return new MyPromise((resolve) => { resolve(parameter) }) }
static reject(reason) { return new MyPromise((resolve, reject) => { reject(reason) }) } }
function resolvePromise(promise, x, resolve, reject) { if (promise === x) { return reject( new TypeError('Chaining cycle detected for promise #<Promise>') ) } if (x instanceof MyPromise) { x.then(resolve, reject) } else { resolve(x) } }
|
面向 Promise A+ 规范
为了更加符合 Promise A+ 的要求,需要对 resolvePromise
进行优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| function resolvePromise(promise, x, resolve, reject) { if (promise === x) { return reject( new TypeError('Chaining cycle detected for promise #<Promise>') ) } if (typeof x === 'object' || typeof x === 'function') { if (x === null) { return resolve(x) }
let then try { then = x.then } catch (error) { return reject(error) }
if (typeof then === 'function') { let called = false try { then.call( x, (y) => { if (called) return called = true resolvePromise(promise, y, resolve, reject) }, (r) => { if (called) return called = true reject(r) } ) } catch (error) { if (called) return reject(error) } } else { resolve(x) } } else { resolve(x) } }
|
参考资料
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://juejin.cn/post/6945319439772434469#comment