本文代码参考 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() |