注:经过作者的进一步学习,觉得有更好的串行Promise的实现,有兴趣的读者可以移步这里
串行的Promise,质的飞跃
接下来要介绍的,是Promise最为有趣与神秘的功能——串行Promise。
它的效果是当前promise达到fulfilled状态之后,会开始下一个promise。例如ajax获取用户id后,再根据用户id获取用户的其他信息,比如:
function p1() {
return new Promise(function(resolve, reject) {
$.get("/userId", function(id) {
resolve(id);
});
});
}
function p2(id) {
return new Promise(function(resolve, reject) {
$.get("/userInfo?id=" + id, function(info) {
resolve(info);
});
});
}
p1().then(p2).then(function(info) {
console.log(info); // 此处输出用户信息
});
这个方法的难点在于,如何衔接当前promise与后邻promise,这需要对then方法进行彻底的改造:
this.then = function(onFulfilled) {
return new Promise(function(resolve) {
handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
}
function handle(deferred) {
if(state === "pending") {
deferreds.push(deferred);
return;
}
let ret = deferred.onFulfilled(value); // 【核心3】,resolve作为onFulfilled传入的情况
if(ret) {
deferred.resolve(ret); // 【核心1】,onFulfilled有返回值的情况,且ret有可能为promise
} else {
deferred.resolve(value); // 【核心4】,onFulfilled无返回值的情况
}
}
为了衔接前后两个promise,让then返回了一个桥接的promise,并添加handle方法来处理onFulfilled。因为此时的onFulfilled很可能会返回一个Promise实例,所以我们需要继续改造resolve方法,用于处理参数为promise的情况。这也是最后的冲刺!
function resolve(newValue) {
if(newValue && (typeof newValue === "object" || typeof newValue === "function") {
let then = newValue.then;
then.call(newValue, resolve); // 【核心2】
return;
} else {
state = "fulfilled";
value = newValue;
setTimeout(() => {
deferreds.forEach((deferred) => {
handle(deferred);
});
}, 0);
}
}
现在,我们的resolve支持传入一个Promise实例了,而且针对promise与普通值的不同,它会执行两条互不干扰的支线方法。
此时我们回到开头的例子:
function p1() {
return new Promise(function(resolve, reject) {
$.get("/userId", function(id) {
resolve(id);
});
});
}
function p2(id) {
return new Promise(function(resolve, reject) {
$.get("/userInfo?id=" + id, function(info) {
resolve(info);
});
});
}
p1().then(p2).then(function(info) {
console.log(info); // 此处输出用户信息
});
p1异步操作获取用户id成功后,会resolve(id),因为id为普通值,所以不会触发第一个为promise所准备的支线方法,而是会去依次handle由then注册的回调
现在的then方法已非吴下阿蒙,它会返回一个用于桥接的promise,但是其引用的handle还是属于p1的,在pending阶段,handle将onFulfilled(即为p2)与桥接promise的resolve组成一个对象,添加到了p1的deferreds队列中
p1的resolve会将deferreds队列依次handle,注意 核心1 ,此时deferred.resolve中的resolve是then返回的桥接promise的resolve,而它成功resolve后,才会继续执行后续的then。注意,此时虽然.then仍然是链式的写法,但每一次then都同步返回了一个新的promise,所以每个then的上下文是不同的(这部分我自己理解了好久)
桥接promise开始resolve了(核心1 代码处),此时的ret正是一个p2的实例,所以会进行第一条支线,这就到达了 核心2 。注意,这里调用了p2实例的then方法注册了一个回调函数,而这个回调函数竟然是resolve,而这个resolve是哪个??太多resolve难免会晕,没错,这个resolve就是当前上下文的桥接promise的resolve,也就是p1.then所新生成的promise的resolve(这个我也理解了好久)
而更令人容易混乱的是,p2实例的then方法也会生成一个promise实例,并且同样会将onFulfilled与resolve组成一个对象,添加到了它的deferreds队列中。不同的是,此处的onFulfilled其实是上一条所说的resolve,而此处的resolve由于p2实例没有后续的then方法已经失去了意义,不需要关心它是否会执行了
代码执行到这里,p1已经resolve成功了,但是p1.then所生成的promise并没有真正resolve,因为它卡在了第一条支线,把resolve方法作为onFulfilled参数传给了p2,只有等待p2完成resolve才能继续,这就是后邻Promise能够等待前一个Promise异步操作完成再执行的奥秘了
我们迎来了p2的resolve,由于是普通值,所以对deferreds队列依次handle,执行到 核心3 的时候,onFulfilled即为刚刚提到的resolve,所以我们的p1.then所生成的promise终于完成resolve了,庆祝一下!
我们先不要离开p2的handle过程, 核心3 后面的代码会继续执行,由于ret为空,会deferred.resolve(value),但是实际上这是没有意义的,因为resolve只有通过then注册了回调才会发生一些事情,可p2.then()是没有后续的then的,所以可以放心的离开了!
然后我们兜了一圈,又回到了p1.then所生成的promise的resolve方法,此时的newValue又称为普通值了,所以又可以对deferreds队列依次handle了!而deferreds队列中就是第二个.then所注册的普通回调函数,于是打印用户信息成功了!
最后需要关注一下 核心4,这里其实是给类似最后一个.then的普通回调函数所准备的,如果ret为空,则让桥接promise直接resolve它的value,这样可以保证.then的链式的继续
说的很啰嗦,但这也是我整个自己理解的过程,因为如果不啰嗦一点,确实很容易被其中的细节所迷惑。到现在为止,我们的Promise只差rejected状态没有处理了,如果上面能够很好的理解,接下来的就没什么难度了。
参考资料:剖析 Promise 之基础篇