本文代码参考 https://tech.meituan.com/promise_insight.html
主要是为了分析实现一个 promise 的步骤
乞丐版
话不多说,一个最简单的 Promise
很简单就出来了:
1 | function YouPromise (fn) { |
测试一下:
1 | it('support chain', cb => { |
但是一旦传入 YouPromise
的函数里面 resolve
是同步执行的话,问题就来了。因为 resolve
先于 then
执行,而 resolve
执行时 deferreds
数组中还没有函数,而等到 then
里面的函数放到数组中时,resolve
都已经执行完了,这些函数也就没有机会再执行了。所以,下面的测试用例会报错
1 | it('support promise synchronous', cb => { |
支持同步 resolve
为了解决上面的问题,我们可以想到在 YouPromise
中,将 resolve
中的代码延迟执行:
1 | function resolve (value) { |
这样,就解决了上面的问题。不过,现在的 YouPromise
有个问题是,在 resolve
执行完之后使用 then
加入的函数不会执行,但是标准的 Promise
是可以的,比如下面这样:
1 | it('can call then after promise resolve', cb => { |
把上面的 Promise
替换成 YouPromise
则测试用例无法通过。
支持 resolve 后还能执行 then
为了支持 resolve
后还能执行 then
里面的函数,我们需要引入状态的概念:
1 | function YouPromise (fn) { |
一个 YouPromise
对象的状态初始化为 pending, 当其状态为 pending 时调用 then
会继续往 deferreds
数组中存放函数,否则说明该对象已经 fulfilled 了,可以直接执行 then
传入的函数。resolve
会将该对象的状态置为 fulfilled。现在再运行上面的测试用例就没问题了。
then 传递结果
为了支持 then
中返回的数据可以传递到下一个 then
,我们的代码要稍作改动:
1 | function resolve (newValue) { |
这样,下面的测试用例就可以顺利通过啦:
1 | const p1 = new Promise((resolve) => { |
串行 promise
接下来就是我们最难的功能了,我们想串行化 promise
,比如像下面这样:
1 | let user = {} |
首先,直接把代码贴出来,然后我们再分析一下这个例子:
1 | function YouPromise (fn) { |
getUserId()
生成了一个 promise,我们把它叫做 promiseGetUserId,promiseGetUserId.then(getUserMobileById)
返回了一个新的 promise,这个 promise 我们把它叫做 promiseBridge1,意思就是作为一个桥梁,连接两个 promise。后面那个.then(printUser)
的调用者,自然就是 promiseBridge1 了,这里又会生成一个 promise,我们叫它 promiseBridge2。两个then
执行完后,此时各 promise 的状态如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17promiseGetUserId: {
value: null,
state: 'pending',
deferreds: [{
onFulfilled: getUserMobileById,
resolve: resolve // 这里的 resolve 是属于 promiseBridge1 的
}]
}
promiseBridge1: {
value: null,
state: 'pending',
deferreds: [{
onFulfilled: printUser,
resolve: resolve // 这里的 resolve 是属于 promiseBridge2 的
}]
}10 毫秒后 promiseGetUserId 执行
resolve
,会依次将deferreds
中的数据放到handle
中执行,该函数中首先执行deferred.onFulfilled(value)
方法,即getUserMobileById
,返回的ret
为一个 promise,我们叫它 promiseGetUserMobileById。然后执行
deferred.resolve(ret)
。我们看resolve
方法,此时newValue
即为 promiseGetUserMobileById,if
中的判断生效,我们调用 promiseGetUserMobileById 的then
方法,并将resolve
作为回调函数传入,这里的resolve
仍然是属于 promiseBridge1 的,同时直接返回。这样就必须等到 promiseGetUserMobileByIdresolve
后才能再次执行 promiseBridge1 的resolve
方法了。此时 promiseGetUserMobileById 的状态如下:1
2
3
4
5
6
7
8promiseBridge1: {
value: null,
state: 'pending',
deferreds: [{
onFulfilled: resolve, // 这里的 resolve 是这段代码传入的 then.call(newValue, resolve) 属于 promiseBridge1
resolve: resolve // 这是另外一个 bidgePromise 的 resolve 了
}]
}20 毫秒后 promiseGetUserMobileById 执行
resolve
方法,此时会依次将deferreds
中的数据放到handle
中执行,最终会执行到 promiseBridge1 的resolve
。再次执行到 promiseBridge1 的
resolve
时,此时newValue
是user
对象,则会执行if
后面的流程,最终会执行printUser
。
失败处理
还是直接贴上代码然后我们再分析一下:
1 | function YouPromise (fn) { |
我们以下面这个例子为例进行分析:
1 | getUserId = () => { |
前面创建 promise,然后执行 then
的过程是一样的,我们来看看 reject
以后发生了什么:
- 将当前 promise 的状态置为
rejected
,然后将失败原因复制为value
,最后调用了finale()
- 该函数其实就是封装了前面的延迟执行
deferreds
的逻辑,执行handle()
发现此时状态不为fulfilled
,且deferred.onRejected
不为空(其实就是 err => {…}),将函数赋值给cb
,然后执行。 - 最后执行 promiseBridge1 的
resolve()
方法
这里,这段代码是为了处理没有传入错误处理函数的情况:
1 | if (cb === null) { |
当 then()
中没有传入错误处理函数时,会直接执行 promiseBridge1 的 reject()
方法,将错误往下传递:
1 | getUserId() |
异常处理
如果在执行成功回调、失败回调时代码出错怎么办?对于这类异常,可以使用 try-catch
捕获错误,并将 bridgePromise 设为 rejected
状态。handle()
方法改造如下:
1 | function handle(deferred) { |
这样就可以处理这种情况了:
1 | getUserId() |