之前重新梳理了一下 JavaScript 中的 Promise 的相关内容,也算是又温习了一遍 Promise
相关知识点,本章当中我们就来看看关于 Promise
的最后一部分内容,也就是 Promise
的内部实现原理
参考了网上各路大神的实现方式,发现虽然实现方式各有不同,但是原理都是十分类似的,所以下面就让我们站在巨人的肩膀上来实现一个我们自己版本的 Promise
,尽量做到浅显易懂
基本结构 我们先来看一个官方版本的 Promise
的简单使用方式,如下
1 2 3 4 5 6 7 const p1 = new Promise ((resolve, reject ) => { setTimeout(() => { resolve('result' ) }, 1000 ) }) p1.then(res => console .log(res), err => console .log(err))
观察上面这个示例我们可以发现,构造函数 Promise
必须接受一个函数作为参数,我们称该函数为 executor
,executor
又包含 resolve
和 reject
两个参数,它们是两个函数,而内部的异步任务(这里是 setTimeout()
)则会被放入对应任务队列,等待我们去调用 then()
来进行执行
所以我们可以明显的意识到这其实就是一个观察者(收集依赖 => 触发通知 => 取出依赖执行)模式,所以我们可以依据这个流程得出我们的 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 class MyPromise { constructor (executor) { if (!isFunction(executor)) { throw new Error (`MyPromise must accept a function as a parameter` ) } this ._fulfilledQueues = [] this ._rejectedQueues = [] try { executor(this ._resolve.bind(this ), this ._reject.bind(this )) } catch (err) { this ._reject(err) } } _resolve(val) { while (this ._fulfilledQueues.length) { const callback = this ._fulfilledQueues.shift() callback(val) } } _reject(val) { while (this ._rejectedQueues.length) { const callback = this ._rejectedQueues.shift() callback(val) } } }
这里的 isFunction
就是一个简单的判断参数是否为函数的方法,如下
1 const isFunction = fn => typeof fn === 'function'
我们建立了两个回调队列,将每次 then
方法注册时的回调函数添加到数组中等待执行,之所以使用一个队列来储存回调,是因为我们知道 then
方法可以被同一个 Promise
调用多次,所以如果使用一个变量而非队列来储存回调,那么即使多次 p1.then()
也只会执行一次回调,其次当 resolve
或 reject
方法执行时,我们可以依次提取成功或失败任务队列当中的函数来进行执行,并清空队列,从而实现 then
方法的多次调用
但是我们在平常在使用 Promise
的时候,都知道它内部是含有多个状态的,所以下面我们再来看看如何给我们的 Promise
添加状态
Promises/A+ 规范 我们都知道,Promise
对象存在以下三种状态
Pending
(进行中)
Fulfilled
(已成功)
Rejected
(已失败)
之所以有这几种状态,是因为 ES6
中的 Promise
的实现需要遵循 Promises/A+ 规范,是规范对 Promise
的状态控制做了要求,规范当中涉及到的内容比较多,在这里我们只总结两条核心规则,如下
Promise
本质是一个状态机,且状态只能为以下三种,即 Pending
(等待态)、Fulfilled
(执行态)和 Rejected
(拒绝态),状态的变更是单向的,只能从 Pending => Fulfilled
或者 Pending => Rejected
,并且状态的变更不可逆
then
方法接收两个可选参数,分别对应状态改变时触发的回调,then
方法返回一个新的 Promise
,并且可以被同一个 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 44 45 46 47 48 49 50 const isFunction = fn => typeof fn === 'function' const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'REJECTED' class MyPromise { constructor (executor) { if (!isFunction(executor)) { throw new Error (`MyPromise must accept a function as a parameter` ) } this ._status = PENDING this ._value = undefined this ._fulfilledQueues = [] this ._rejectedQueues = [] try { executor(this ._resolve.bind(this ), this ._reject.bind(this )) } catch (err) { this ._reject(err) } } _resolve(val) { const run = () => { if (this ._status !== PENDING) return this ._status = FULFILLED this ._value = val while (this ._fulfilledQueues.length) { const callback = this ._fulfilledQueues.shift() callback(val) } } setTimeout(run) } _reject(err) { const run = () => { if (this ._status !== PENDING) return this ._status = REJECTED this ._value = err while (this ._rejectedQueues.length) { const callback = this ._rejectedQueues.shift() callback(val) } } setTimeout(run) } }
为了支持同步的 Promise
,我们将之前同步的回调放到了一个 run
函数当中,并且采用了异步调用的方式,这样一来我们就实现了 Promise
状态和值的改变,下面我们再来看看 Promise
的核心方法 then
的实现
then 方法 Promise
对象的 then
方法接受两个参数,一个成功的回调和一个失败的回调
1 promise.then(onFulfilled, onRejected)
我们还是和上面一样,根据 Promises/A+ 规范来梳理一下 then
方法实现当中需要注意的地方,首先 onFulfilled
和 onRejected
都是可选参数,如果 onFulfilled
或 onRejected
不是函数,需要被忽略
onFulfilled
的特性
如果 onFulfilled
不是函数,忽略
当 Promise
状态变为成功时必须被调用,其第一个参数为 Promise
成功状态传入的值(resolve
执行时传入的值)
在 Promise
状态改变前其不可被调用
其调用次数不可超过一次
onRejected
的特性
如果 onRejected
不是函数,忽略
当 Promise
状态变为失败时必须被调用,其第一个参数为 Promise
失败状态传入的值(reject
执行时传入的值)
在 Promise
状态改变前其不可被调用
其调用次数不可超过一次
其次我们还需要注意多次调用的情况,因为 then
方法可以被同一个 Promise
对象调用多次,所以在这里需要注意
当 Promise
成功状态时,所有 onFulfilled
需按照其注册顺序依次回调
当 Promise
失败状态时,所有 onRejected
需按照其注册顺序依次回调
最后需要注意的就是 then
方法调用以后的返回值
1 promise2 = promise1.then(onFulfilled, onRejected)
因为 then
方法必须返回一个新的 Promise
对象,所以 Promise
才可以支持链式调用
1 promise1.then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2)
关于多次调用和返回值,因为这里涉及到了 Promise
的执行规则,也就是值的传递和错误捕获机制,所以这里我们借住规范来详细梳理一下,主要有以下几点
如果 onFulfilled
或者 onRejected
返回一个值 x
,则运行下面的 Promise
解决过程([[Resolve]](promise2, x)
)
若 x
不为 Promise
,则使 x
直接作为新返回的 Promise
对象的值,即新的 onFulfilled
或者 onRejected
函数的参数
若 x
为 Promise
,这个时候后一个回调函数就会等待该 Promise
对象(即 x
)的状态发生变化才会被调用,并且新的 Promise
状态和 x
的状态相同
其实简单来说就是 then
方法的返回值分为普通值和 Promise
对象两种情况,所以需要进行不同的处理,对比以下两个示例,我们就能很明显的发现其中的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let promise1 = new Promise ((resolve, reject ) => { setTimeout(() => { resolve() }, 1000 ) }) promise2 = promise1.then(res => { return '这里返回一个普通值' }) promise2.then(res => { console .log(res) })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let promise1 = new Promise ((resolve, reject ) => { setTimeout(() => { resolve() }, 1000 ) }) promise2 = promise1.then(res => { return new Promise ((resolve, reject ) => { setTimeout(() => { resolve('这里返回一个 Promise' ) }, 2000 ) }) }) promise2.then(res => { console .log(res) })
如果 onFulfilled
或者 onRejected
抛出一个异常 e
,则 promise2
必须变为失败(Rejected
),并返回失败的值 e
,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let promise1 = new Promise ((resolve, reject ) => { setTimeout(() => { resolve('success' ) }, 1000 ) }) promise2 = promise1.then(res => { throw new Error ('这里抛出一个异常 e' ) }) promise2.then(res => { console .log(res) }, err => { console .log(err) })
如果 onFulfilled
不是函数且 promise1
状态为成功(Fulfilled
),promise2
必须变为成功(Fulfilled
)并返回 promise1
成功的值,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let promise1 = new Promise ((resolve, reject ) => { setTimeout(() => { resolve('success' ) }, 1000 ) }) promise2 = promise1.then('这里的 onFulfilled 本来是一个函数,但现在不是' ) promise2.then(res => { console .log(res) }, err => { console .log(err) })
针对于这种情况,简单来说就是 then
方法接收的参数如果不是函数,那么我们应该忽略它,如果没有忽略的话,当 then
方法回调不为函数的时候将会抛出异常,导致链式调用中断
如果 onRejected
不是函数且 promise1
状态为失败(Rejected
),promise2
必须变为失败(Rejected
)并返回 promise1
失败的值,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let promise1 = new Promise ((resolve, reject ) => { setTimeout(() => { reject('fail' ) }, 1000 ) }) promise2 = promise1.then(res => res, '这里的 onRejected 本来是一个函数,但现在不是' ) promise2.then(res => { console .log(res) }, err => { console .log(err) })
其实梳理下来我们可以发现,上面的一些处理方式,其实都是为了解决状态分别为 resolve/reject
下的不同情况,因为在有些时候 resolve/reject
可能在 then()
之前就被执行(比如 Promise.resolve().then()
),如果这个时候我们还把 then()
回调 push
到 resolve/reject
的执行队列里,那么回调将不会被执行,因此对于状态已经变为 fulfilled
或 rejected
的情况,我们需要单独来进行处理,所以下面我们就来看看到底该如何实现
在了解了以上总结的一些规则以后,我们就可以得出 then
方法的大概雏形,首先 then
方法接受两个函数作为参数,然后返回一个新的 Promise
对象,并且需要将回调函数加入到执行队列中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 then(onFulfilled, onRejected) { const { _value, _status } = this switch (_status) { case PENDING: this ._fulfilledQueues.push(onFulfilled) this ._rejectedQueues.push(onRejected) break case FULFILLED: onFulfilled(_value) break case REJECTED: onRejected(_value) break } return new MyPromise((onFulfilledNext, onRejectedNext ) => { }) }
这个时候我们就需要考虑一下这个返回的对象了,首先返回的新的 Promise
对象肯定是包含两个函数的,也就是成功时执行的函数和失败时执行的函数,但是我们什么时候改变状态呢?改变为哪种状态呢?
根据上面 then
方法当中的规则,我们知道返回的新的 Promise
对象的状态依赖于当前 then
方法回调函数执行的情况以及返回值,例如 then
方法的参数是否为一个函数、回调函数执行是否出错、返回值是否为 Promise
对象等等,也就是说,在这里我们就不能简单的针对于成功或者失败的状态而去直接调用传递进来的 onFulfilled()
或者 onRejected()
方法了,而是需要根据当前状态的不同,进行不同的处理
如下,我们将对应的 onFulfilled()
和 onRejected()
包裹一层,也就是下面的 fulfilled
和 rejected
方法,在其中我们针对当前状态的不同来进行不同的处理
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 then(onFulfilled, onRejected) { const { _value, _status } = this return new MyPromise((onFulfilledNext, onRejectedNext ) => { let fulfilled = value => { try { if (!isFunction(onFulfilled)) { onFulfilledNext(value) } else { let res = onFulfilled(value) if (res instanceof MyPromise) { res.then(onFulfilledNext, onRejectedNext) } else { onFulfilledNext(res) } } } catch (err) { onRejectedNext(err) } } let rejected = error => { try { if (!isFunction(onRejected)) { onRejectedNext(error) } else { let res = onRejected(error) if (res instanceof MyPromise) { res.then(onFulfilledNext, onRejectedNext) } else { onFulfilledNext(res) } } } catch (err) { onRejectedNext(err) } } switch (_status) { case PENDING: this ._fulfilledQueues.push(fulfilled) this ._rejectedQueues.push(rejected) break case FULFILLED: fulfilled(_value) break case REJECTED: rejected(_value) break } }) }
最后我们再来考虑处理一种特殊情况,那就是如果 _resolve
方法传入的参数为一个 Promise
对象,并且此时该 Promise
对象状态决定当前 Promise
对象的状态,也就是下面这个示例当中所示
1 2 3 4 5 6 7 8 const p1 = new Promise (function (resolve, reject ) { }) const p2 = new Promise (function (resolve, reject ) { resolve(p1) })
上面代码中,p1
和 p2
都是 Promise
的实例,但是 p2
的 resolve
方法将 p1
作为参数,即一个异步操作的结果是返回另一个异步操作,这时 p1
的状态就会传递给 p2
,也就是说 p1
的状态决定了 p2
的状态,也就是
如果 p1
的状态是 Pending
,那么 p2
的回调函数就会等待 p1
的状态改变
如果 p1
的状态已经是 Fulfilled
或者 Rejected
,那么 p2
的回调函数将会立刻执行
所以下面我们就来修改我们开头部分的 _resolve
方法以便支持这样的特性,我们将 _resolve
当中单纯的执行成功队列中的函数的方式拆分了一下,分为了 runFulfilled
和 runRejected
两种情况,然后根据传递进 _resolve
方法的参数不同来分别赋予不同的状态和执行逻辑
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 _resolve(val) { const run = () => { if (this ._status !== PENDING) return const runFulfilled = (value ) => { while (this ._fulfilledQueues.length) { const callback = this ._fulfilledQueues.shift() callback(value) } } const runRejected = (error ) => { while (this ._rejectedQueues.length) { const callback = this ._rejectedQueues.shift() callback(error) } } if (val instanceof MyPromise) { val.then(value => { this ._value = value this ._status = FULFILLED runFulfilled(value) }, err => { this ._value = err this ._status = REJECTED runRejected(err) }) } else { this ._value = val this ._status = FULFILLED runFulfilled(val) } } setTimeout(run, 0 ) }
至此,我们已经实现了 Promise
的主要功能,但是原生的 Promise
还提供了一些额外方法,下面我们来看看这些附加方法如何实现
catch() 我们在之前的 JavaScript 中的 Promise 章节当中的曾经提到过,实际上 catch()
只是 promise.then(undefined, onRejected)
方法的一个别名而已,但是需要注意的是 catch()
方法也返回一个 Promise
,也就是说 catch
之后,我们还是可以继续是使用 then
方法
1 2 3 4 catch (onRejected) { return this .then(undefined , onRejected) }
resolve() Promise.resolve(value)
方法返回一个以给定值解析后的 Promise
对象
如果 value
是个 Thenable
对象,返回的 Promise
会跟随这个 Thenable
的对象,采用它的最终状态(关于 Thenable
对象可以参考 JavaScript 中的 Promise )
如果传入的 value
本身就是 Promise
对象,那么 Promise.resolve
将不做任何修改、原封不动地返回这个 Promise
对象
其他情况则直接返回以该值为成功状态的 Promise
对象
1 2 3 4 5 static resolve(value) { if (value instanceof MyPromise) return value return new MyPromise(resolve => resolve(value)) }
reject() 与 Promise.resolve()
不同的是,Promise.reject()
方法的参数会原封不动地作为 reject
的理由,变成后续方法的参数
1 2 3 static reject(value) { return new MyPromise((resolve, reject ) => reject(value)) }
all() Promise.all(promises)
返回一个 Promise
对象
如果传入的参数是一个空的可迭代对象,那么此 Promise
对象回调完成(resolve
),只有此情况是同步执行的,其它都是异步返回的
如果传入的参数不包含任何 Promise
,则返回一个异步完成
所有的 Promise
都完成时或参数中不包含 Promise
时回调完成
如果参数中有一个 Promise
失败,那么 Promise.all
返回的 Promise
对象失败,失败原因是第一个失败 Promise
的结果
在任何情况下,Promise.all
返回的 Promise
的完成状态的结果都是一个数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static all(list) { return new MyPromise((resolve, reject ) => { let values = [] let count = 0 for (let [i, p] of list.entries()) { this .resolve(p).then(res => { values[i] = res count++ if (count === list.length) resolve(values) }, err => { reject(err) }) } }) }
race() Promise.race()
函数返回一个 Promise
,它将与第一个传递的 Promise
相同的完成方式被完成,它可以是完成(resolves
),也可以是失败(rejects
),这要取决于第一个完成的方式是两个中的哪个,如果传的参数数组是空,则返回的 Promise
将永远等待
1 2 3 4 5 6 7 8 9 10 11 12 static race(list) { return new MyPromise((resolve, reject ) => { for (let p of list) { this .resolve(p).then(res => { resolve(res) }, err => { reject(err) }) } }) }
finally() finally()
方法返回一个 Promise
,在 Promise
结束时,无论结果是 fulfilled
或者是 rejected
,都会执行指定的回调函数,在 finally
之后,我们还可以继续调用 then
方法,并且会将值原封不动的传递给后面的 then
方法
1 2 3 4 5 6 finally (callback) { return this .then( value => MyPromise.resolve(callback()).then(() => value), reason => MyPromise.resolve(callback()).then(() => { throw reason }) ) }
这里针对 MyPromise.resolve(callback())
我们详细说明一下,这个写法其实涉及到一个 finally()
的使用细节,即 finally()
如果 return
了一个 reject
状态的 Promise
,将会改变当前 Promise
的状态,这个 MyPromise.resolve()
就用于改变 Promise
状态,在 finally()
没有返回 reject
状态的 Promise
或 throw
错误的情况下,去掉 MyPromise.resolve
也是一样的
比如下面这个例子
1 2 3 4 5 6 7 8 9 var p = Promise .resolve('ok' ) .finally(() => { return Promise .reject('这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议' ) }) .then(value => { console .log('成功' , value) }, (err) => { console .log('失败' , err) })
finally()
对自身返回的 Promise
的决议影响有限,它可以将上一个 resolve
改为 reject
,也可以将上一个 reject
改为另一个 reject
,但不能把上一个 reject
改为 resolve
,更多使用细节可以参考 Promise.prototype.finally()
完整代码 以上,我们就实现了一个完整的 Promsie
,更为完善的实现可以参考 es6-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 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 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 const isFunction = fn => typeof fn === 'function' const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'REJECTED' class MyPromise { constructor (executor) { if (!isFunction(executor)) { throw new Error ('MyPromise must accept a function as a parameter' ) } this ._status = PENDING this ._value = undefined this ._fulfilledQueues = [] this ._rejectedQueues = [] try { executor(this ._resolve.bind(this ), this ._reject.bind(this )) } catch (err) { this ._reject(err) } } _resolve(val) { const run = () => { if (this ._status !== PENDING) return const runFulfilled = (value ) => { while (this ._fulfilledQueues.length) { const callback = this ._fulfilledQueues.shift() callback(value) } } const runRejected = (error ) => { while (this ._rejectedQueues.length) { const callback = this ._rejectedQueues.shift() callback(error) } } if (val instanceof MyPromise) { val.then(value => { this ._value = value this ._status = FULFILLED runFulfilled(value) }, err => { this ._value = err this ._status = REJECTED runRejected(err) }) } else { this ._value = val this ._status = FULFILLED runFulfilled(val) } } setTimeout(run, 0 ) } _reject(err) { const run = () => { if (this ._status !== PENDING) return this ._status = REJECTED this ._value = err while (this ._rejectedQueues.length) { const callback = this ._rejectedQueues.shift() callback(err) } } setTimeout(run, 0 ) } then(onFulfilled, onRejected) { const { _value, _status } = this return new MyPromise((onFulfilledNext, onRejectedNext ) => { let fulfilled = value => { try { if (!isFunction(onFulfilled)) { onFulfilledNext(value) } else { let res = onFulfilled(value) if (res instanceof MyPromise) { res.then(onFulfilledNext, onRejectedNext) } else { onFulfilledNext(res) } } } catch (err) { onRejectedNext(err) } } let rejected = error => { try { if (!isFunction(onRejected)) { onRejectedNext(error) } else { let res = onRejected(error) if (res instanceof MyPromise) { res.then(onFulfilledNext, onRejectedNext) } else { onFulfilledNext(res) } } } catch (err) { onRejectedNext(err) } } switch (_status) { case PENDING: this ._fulfilledQueues.push(fulfilled) this ._rejectedQueues.push(rejected) break case FULFILLED: fulfilled(_value) break case REJECTED: rejected(_value) break } }) } catch (onRejected) { return this .then(undefined , onRejected) } static resolve(value) { if (value instanceof MyPromise) return value return new MyPromise(resolve => resolve(value)) } static reject(value) { return new MyPromise((resolve, reject ) => reject(value)) } static all(list) { return new MyPromise((resolve, reject ) => { let values = [] let count = 0 for (let [i, p] of list.entries()) { this .resolve(p).then(res => { values[i] = res count++ if (count === list.length) resolve(values) }, err => { reject(err) }) } }) } static race(list) { return new MyPromise((resolve, reject ) => { for (let p of list) { this .resolve(p).then(res => { resolve(res) }, err => { reject(err) }) } }) } finally (callback) { return this .then( value => MyPromise.resolve(callback()).then(() => value), reason => MyPromise.resolve(callback()).then(() => { throw reason }) ) } }
最后我们来简单的测试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const p1 = new MyPromise((resolve, reject ) => { resolve(1 ) }) p1.then(res => { console .log(res) return 2 }).then('123' ).then(res => { console .log(res) return new MyPromise((resolve, reject ) => { resolve(3 ) }) }).then(res => { console .log(res) throw new Error ('reject测试' ) }).then(() => { }, err => { console .log(err) })
输出结果如下
参考