前言
解读了同步执行 resolve 的代码,接下来要看的则是异步执行 resolve了。异步总会比同步复杂得多,它不会按照顺序执行,所以代码会跳来跳去地阅读。
与同步不同的是,异步时代码有可能会先执行 then 函数,将 then 的回调函数保存起来,等到执行 resolve 的时候,再将其取出执行。
new Promise(function (resolve) {
setTimeout(function () {
resolve(1);
}, 1000);
}.then(function (val) {
console.log(val);
});
复制代码
注:本次阅读的是 then/promise 的 3.0.0 版本,源码请戳 这里。
解读
这一次的解读会按照以下的一个执行顺序来进行:
constructor -> fn --异步--> then -> resolve(reject) -> then 回调
复制代码
then
从 fn 函数执行后开始,这时来到了 then
函数:
this.then = function(onFulfilled, onRejected) {
return new Promise(function(resolve, reject) {
handle({ onFulfilled: onFulfilled, onRejected: onRejected, resolve: resolve, reject: reject })
})
}
复制代码
同样先不用管返回的 Promise 实例和它的参数,我们只需要知道 onFulfilled 和 onRejected 被作为参数传递给了 handle
函数。可以把以上代码简化成以下:
this.then = function(onFulfilled, onRejected) {
handle({ onFulfilled: onFulfilled, onRejected: onRejected })
}
复制代码
handle
来看看 handle
函数是怎么处理 onFulfilled 函数(即 then 的回调函数)的。简化了许多代码:
function handle(deferred) {
if (state === null) {
deferreds.push(deferred)
return
}
}
复制代码
state
是构造函数里定义的一个变量,它主要作用是用来记录状态,执行 resolve 成功赋值 true,执行 reject 成功赋值 false,初始为 null。
所以此时还没调用 resolve(reject),state 就为 null。这时就用另一个变量 deferreds
来保存 handle 传递进来的参数,即 then 的回调函数。
deferreds 是一个数组,由于我们可以多次调用 then 函数,所以需要一个数组来保存那些回调函数。类似于这样的:
var p = new Promise(function (resolve) {
setTimeout(function () {
resolve(1);
}, 1000);
}
p.then(function (val) {
console.log(val);
});
p.then(function (val) {
console.log(val + 1);
});
复制代码
至此,then 的回调函数已经被保存起来了,就等着异步执行完毕后 resolve
被调用了。
resolve
假设异步执行完毕了,开始调用 resolve
函数。
function resolve(newValue) {
resolve_(newValue)
}
function resolve_(newValue) {
if (state !== null)
return
try {
state = true
value = newValue
finale()
} catch (e) { reject_(e) }
}
复制代码
同样简化了一下代码,去掉暂时不用到的。state
保存 resolve 后的状态,value
保存 resolve 的参数。然后调用 finale
函数。
finale
来到 finale
函数,它将取出之前 deferreds
数组保存的 then 回调函数,再传给 handle
函数,让 handle 函数来执行。
function finale() {
for (var i = 0, len = deferreds.length; i < len; i++)
handle(deferreds[i])
deferreds = null
}
复制代码
handle
好了,又回到了 handle
函数,这一次代码跟刚刚的不一样了。以下代码展示忽略 deferred.resolve 和 deferred.reject 的调用,我们的关注点在于 onFulfilled
:
function handle(deferred) {
nextTick(function() {
var cb = state ? deferred.onFulfilled : deferred.onRejected
if (typeof cb !== 'function'){
(state ? deferred.resolve : deferred.reject)(value)
return
}
var ret
try {
ret = cb(value)
}
catch (e) {
deferred.reject(e)
return
}
deferred.resolve(ret)
})
}
复制代码
嗯,是的,这一步跟同步执行 resolve 的最后一步是一样的。说实话这里的 nextTick 我也还没搞懂为啥要用,感觉去掉也不影响使用。
不过不影响整体的阅读,现在知道这次调用 handle 是为了调用 onFulfilled
函数,即 then 的回调函数被执行了。
handle 函数的完整代码是这样的:
function handle(deferred) {
if (state === null) {
deferreds.push(deferred)
return
}
nextTick(function() {
var cb = state ? deferred.onFulfilled : deferred.onRejected
if (typeof cb !== 'function'){
(state ? deferred.resolve : deferred.reject)(value)
return
}
var ret
try {
ret = cb(value)
}
catch (e) {
deferred.reject(e)
return
}
deferred.resolve(ret)
})
}
复制代码
代码执行了两次 handle 函数,它有两个用处。在 state 为 null 时,它用来保存回调函数 onFulfilled。再次调用它时,resolve 已被执行,state 被修改成 true,则用来执行 onFulfilled。
总结
Promise 异步执行代码时会比较的绕:
new Promise(function (resolve) {
setTimeout(function () {
resolve(1);
}, 1000);
}.then(function (val) {
console.log(val);
});
复制代码
执行的顺序回顾一下:
constructor -> fn --异步--> then -> resolve(reject) -> then 回调
复制代码
执行代码时,会先执行构造函数,然后是传入构造函数的 fn 函数。紧接着是执行 then 函数,将其回调函数 onFulfilled 保存在 deferreds 数组中。
等到 fn 函数里的异步代码执行完毕后,调用 resolve 函数,将保存在 deferreds 数组里的回调函数取出执行。