前言
距离我上次写的那篇Promise实现的基本原理(一)有十几天了(太懒了),这篇我想写Promise是如何实现串联调用then,解决回调地狱的。虽然说《深入理解ES6》这本书把Promise用法讲得很详细,但是自己理解起来还是有很多疑问(我好想上一篇也是这么说的)。
参考链接:JavaScript Promises ... In Wicked Detail
github上的完整实现:Bare bones Promises/A+ implementation
一、Promise可能会出现的异步情况
情况一:对resolve的调用是异步的
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolve')
console.log('Promise setTimeout')
}, 1000)
}).then(result => {
// 这个箭头函数我们叫 fn1
console.log(result)
}, error => {
// 这个箭头函数我们叫 fn2
console.log(error)
})
情况二:对then的调用是异步的
let p1 = new Promise((resolve, reject) => {
resolve('resolve')
console.log('Promise setTimeout')
})
setTimeout(() => {
p1.then(result => {
// 这个箭头函数我们叫 fn1
console.log(result)
}, error => {
// 这个箭头函数我们叫 fn2
console.log(error)
})
}, 1000)
情况三:调用resolve和调用then都是异步的
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolve')
console.log('Promise setTimeout')
}, 1000)
})
setTimeout(() => {
p2.then(result => {
// 这个箭头函数我们叫 fn1
console.log(result)
}, error => {
// 这个箭头函数我们叫 fn2
console.log(error)
})
}, 1000)
第三种情况是前两种情况的结合,如果第一个setTimeout时间比第二个setTimeout时间长,那就是第一种情况;如果第二个setTimeout比第一个setTimeout的时间长,那就是第二种情况。了解event-loop的同学看着几种情况应该比较清晰,不了解的同学就看我下面的分析。
二、Promise三种状态是如何工作的
我从《深入理解ES6》Promise那章截取了一下内容:
每个
Promise都会经历一个短暂的生命周期:先是处于进行中(padding)的状态,此时操作尚未完成,所以他也是未处理(unsettled)的;一旦异步操作执行结束,Promise则变为已处理(settled)的状态,操作结束后Promise可能会进入到以下两个状态的其中一个:
- Fulfilled
Promise异步操作成功完成- Rejected 由于程序错误或者一些其他原因,
Promise异步操作未能成功完成
Promise的内部属性[[PromiseState]]被用来表示Promise的3中状态:"pedding"、"fulfilled"、"rejected"。
实现代码:
function Promise(fn) {
var state = 'pending'; // 初始状态为padding
var value; // Promise处于被处理状态下保存下来的值
var deferred = null; // 延期执行函数(现在还看不出来它是函数)
/**
* 异步操作成功完成回调函数
* 在new Promise的时候被调用的(不清楚为什么的可以看上一篇博客)
*/
function resolve(newValue) {
// 如果在then的回调函数里面返回了一个Promise
if(newValue && typeof newValue.then === 'function') {
// 将这个Promise的then的两个回调参数的引用指向Promise的resolve和reject
newValue.then(resolve, reject);
return;
}
// 如果传进来的不是Promise
state = 'resolved'; // 将Promise的状态改成resolved(fulfilled)
value = newValue; // 保存return过来的值
if(deferred) { // 判断是否存在传进来的回调函数
handle(deferred); // 如果有,将我们传进来的回调函数交给handle
} //(不清楚为什么的可以看上一篇博客)
}
/**
* 异步操作失败/被拒绝回调函数
* 在 new Promise 的时候被调用的
*/
function reject(reason) {
state = 'rejected'; // 将Promise的状态改成rejected
value = reason; // 保存我们传进来的被拒绝原因
if(deferred) { // 和 resolve同理
handle(deferred);
}
}
function handle(handler) {
// handle被调用了,但是此时promise的状态还是处于出事的未处理状态
if(state === 'pending') {
deferred = handler; // 就将handler的引用先保存下来
return; // 不执行下面的代码
}
var handlerCallback; // Promise处理完成的状态只能是一种(fulfilled/rejected),所以用同一个变量保存回调的引用
if(state === 'resolved') { // 如果已经成功处理(fulfilled)
handlerCallback = handler.onResolved; // 引用是处理成功回调的引用
} else {
handlerCallback = handler.onRejected; // 如果不是,引用是处理失败回调的引用
}
if(!handlerCallback) { // 如果在调用then的时候没有给then传回调函数,即上一个if语句保存下来的引用是undefined的
if(state === 'resolved') { // 如果状态已经是resolved(fulfilled)的
handler.resolve(value); // 就调用hander的resolved(下一个then传入的成功回调函数)
} else {
handler.reject(value); // 否则,调用handler的reject(下一个then传入的第二个参数,即失败回调函数)
}
return; // 不执行下面的语句
}
// 如果本次调用的then有传入回调函数
var ret = handlerCallback(value);
handler.resolve(ret); // 就给
}
this.then = function(onResolved, onRejected) {
return new Promise(function fn3 (resolve, reject) { // then 返回一Promise
// 返回的Promise把then传进来的两个回调和本省的两个回调都丢给handle处理
handle({
onResolved: onResolved,
onRejected: onRejected,
resolve: resolve,
reject: reject
});
});
};
fn(resolve, reject);
}
回调函数引用分析:
fn函数的引用在上一篇已经分析过了,这一片重点分析在then中返回Promise给handle函数传过去的对象属性引用
onResolved: 在我们用Promise对象调用then的时候传过来的第一个参数 fn1 (我在注释里给了个名字好区分,就不像第一篇那样一个一个拆出来了,有点懒)。onReject: 在我们用Promise对象调用then的时候传过来的第二个参数fn2。resolve:then返回的Promise对象传过去的fn3, 当fn3被调用时Promise传给它的第一个参数就是resolve的引用,所以resolve(这是key) 的引用是Promise的内部函数resolve(这是function)。当然,每一次new Promise都会是一个新的对象,所以内部的resolve的引用也是不一样的。reject:reject和resolve同理,指向Promise内部函数reject。
handle函数分析
1、handle参数分析
handle函数的调用还是在用户调用then的时候调用,只不过这次then函数返回了一个Promise,给handle函数传入的值既保留了本次调用then传入的两个回调函数的引用,也保留了新的Promise的回调函数引用。
2、结合三种状态分析
(1)"pending"状态:
当调用handle函数时Promise处于padding对应的情况是情况一,此时用户创建了Promise对象后立即调用了then函数,但resolve还没有被处理,所以Promise状态没有改变,then的回调函数需要等待resolve被处理了才可以执行。所以当padding状态的时候,将handler这个参数交给deferred暂存。
(2)"resolved"/rejected状态:
一秒后,resolve回调函数被调用了(关于resolve的指向可以看第一篇),Promise的state被置为"resolved"了,然后只要deferred有指向,就再一次交给handle函数处理一次。
此时Promise的状态是"resolved",将handlerCallback的引用指向fn1,又将handleCallback返回的值保存在了ret中,同时又传给then返回的Promise的resolve,让返回的Promise处于已处理状态(只有当resolve被执行了,then的回调函数才会执行,才能够实现then的无限串联调用),也就是一下这种情况:
let p = new Promise((resolve, reject) => {
resolve(42)
})
p.then().then().then(result => {
console.log(result) // 控制台输出42
})
三、如何实现then的串联使用
上面那一点其实就可以看出,Promise是通过在then函数里面返回了一个Promise,实现了Promise的then的串联使用。但上面的那情况并没有真正体现Promise的作用,请看以下代码:
new Promise((resolve, result) => { // Promise1
resolve(1)
}).then(result => { // Promise1的then
console.log(result) // 1
return new Promise((resolve, reject) => { // Promise3
resolve(2)
})
}) // 此处得到Promise2
.then(result => { // Promise2的then
console.log(result) // 2
return 3
}) // 此处得到Promise4
.then(result => { // Promise4的then
console.log(result) // 3
}) // 此处得到Promise5
在实现代码里面可以看到then时返回了一个新的Promise的,然后再看上面的代码,在调用then的第一个回调函数回调的时候,我们给他返回了一个参数,一种是Promise对象,一种是返回一个数值,到这里我的疑问是then内部返回的Promise是如何拿到这些值的,以下分析👆上面的代码
Promise个数分析
上面那段代码里里外外有5个Promise,先起个小名,分别是:
new的时候有一个 —— Promise1- Promise1的
then内部返回了一个 —— Promise2 - Promise1的
then的第一个回调函数返回了一个 —— Promise3 - Promise2的
then内部返回了一个 —— Promise4 - Promise4的
then内部返回了一个 —— Promise5
1、Promise1:
handler参数四个属性指向:
onResolved: Promise1的then的第一个参数(回调函数)onRejected: Promise1的then的第二个参数(回调函数)resolve: Promise2的内部函数resolvereject: Promise2的内部函数reject
Promise1当前的状态是"resolved"
then被调用,所以handle被调用blablabla~
handleCallback指向handler.onResolved
各种判断赋值后到了handle的最后两行,此时ret接收到handleCallback返回的值,然后这个值是Promise3。接着调用了handle.resolve,并且给它传了ret,而handle.resolve指向的是Promise2的resolve,所以接下来就跳到Promise2了。
2、Promise2:
handler参数四个属性指向:
onResolved: Promise2的内部函数resolveonRejected: Promise2的内部函数rejectresolve: Promise4的内部函数resolvereject: Promise4的内部函数reject
Promise2的resolve在Promise1的handle函数的最后一条语句被调用,并且传入的ret是一个Promise(Promise3)
所以Promise2的resolve函数中的第一个语句成立,但Promise2的状态不改变,newValue指向的是Promise3,需要等待Promise3被处理。
接着Promise3的then被调用,所以接下要调到Promise3了。
3、Promise3
handler参数四个属性指向:
onResolved: Promise2的内部函数resolveonRejected: Promise2的内部函数rejectresolve: Promise3的then返回的Promise的内部函数resolve(在当前例子不是很重要,忽略)reject: Promise3的then返回的Promise的内部函数reject(在当前例子不是很重要,忽略)
在Promise2调用Promise3的then的时候,Promise3的状态未知,但不管怎样,只要Promise的状态改变了,就会调用一次handle函数。
某个时刻Promise3的状态变为了"resolved"/"rejected"或者在Promise2调用它的时候就已经是"resolved"/"rejected"状态
还是到handle函数的最后两行,和Promise1的一样,handler.onResolved会被执行,并且把Promise3的resolve回调时得到的值value传给Promise2的内部函数resolve,Promise2变为了"resolved"状态,并且的到了Promise3被处理后的value。
Promise3此时的任务已经完成了,被处理了,并且把得到的value传给了Promise2,拜拜👋。
4、回到Promise2
handler参数四个属性的指向还是和之前一样,在Promise3被处理之后,Promise2的状态也会跟着Promise3的状态变化,并且拿到了Promise3的value,所以先在Promise2的value是2
Promise2状态变为已处理之后,不管什么时候调用then,then的两个回调函数其中一个会被回调,所以Promise的这种处理,会让我这种菜🐔觉得then的回调函数里面return的Promise是变成了then的return,但其实Promise做的事情只是保存值而已。
5、Promise4
Promise4的handler参数属性指向其实和Promise2是差不多的,和Promise2不同的是:ret的到不是一个Promise,而是一个具体的值。也就是说newValue = 3,newValue不是Promise就好办了,把状态改成"resolved",保存一下return过来的值,其他和最开始的Promise处理方式一样,完事。
总结
Promise能够串联调用then,还是保存值和各种引用的转换,Promise大法好!