最近看一段javascript代码,越看越奇怪,虽然最后问题解决了,但是这段奇怪的代码一直萦绕在我心头,是使用了Promise Chain的代码,今天终于找了点时间,大概了解了下,现在记录下来以备后用。
什么是Promise
字面意思是“承诺”, 官方定义是表示:一个异步事件的完成(失败)以及这个事件的结果。
建议看看MDN's Promise docs,有助于形成一个完整和正确的认识
在javascript中,异步事件的处理一直都是使用回调函数(callback),而Promise正是用来取代callback的。具体Promise的用法不想多说,请查看以上链接。
回到callback 如果若干个异步事件需要按顺序调用的话,就会出现回调函数的嵌套,举个例子, 订披萨和热披萨是两个异步事件,伪代码如下
orderaPizza(function(pizza){
heatPizza(function(hotPizza){
eatPizza(hotPizza);
})
})
watchTV()
函数: orderaPizza() 和 heatPizza() 都是异步操作,我并不知道这些操作什么时候完成,所以当我订了披萨(orderaPizza())以后我就去看电视了(watchTV()), 等披萨送来以后会调用我传入的callback方法:heatPizza(), 这也是个异步方法,披萨热了以后会调用我传入的callback方法eatPizza()。 看起来还不错,异步方法按照顺序执行了(orderPizza() --> heatPizza())。但是如果有更多的异步方法需要顺序执行会怎样? 放一张图镇楼!
这就是所谓的callback hell。
这就是Promise Chain适合的场景,放一段代码:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
这段代码的output是: 1->2-> 4
初看这段代码其实很混乱:.then()是Promise 对象的成员方法,但是除了行(*)以外,在接下来的.then()内部代码都未曾返回Promise对象,而是返回了一些数值,如:return restult * 2,如果是这样,接下来的.then()应该会报错,因为数值类型是没有.then()的。但是这段代码是可以工作的,所以,谨记以下知识点:
1. .then()(称之为handler)会隐含返回一个Promise
2. 如果handler(in .then())显式return了一个值,这个值会作为隐含返回的Promise的结果,就是callback函数的参数(红色底色)
但是这跟若干个异步函数顺序执行有什么关系?
好的,我们接着说,一般来讲,上一个.then()返回的值会立刻传给下一个.then(), 但是有一个例外: 返回的不是一般的值,是个Promise,这种情况下程序就会suspend,等待最近返回的Promise完成才能继续并且这个Promise执行的结果会作为接下来的.then() callback的输入参数(红色底色)。
有木有讲到核心了,但是还是有点晦涩,我们还是看代码:
function orderPizza(){ //*0
return new Promise(function(resolve,reject){ //*1
resolve("Pizza");
}).then(function(res){ //*2
console.log('1-Pay for Pizza');
return res; //*2.1
});
}
function heatPizza() { //*3
return new Promise(function(resolve,reject){ //*4
resolve("hotPizza");
}).then(function(res){ //*5
console.log('3-Could eat?');
return res; //*6
});
}
function eatPizza(){ //*7
console.log("5-Enjoy");
}
orderPizza() //*7.1
.then(function(res){ //*8
console.log("2-Put Pizza in microwave oven");
console.log(res); //*8.1
// return res;
return heatPizza() //*9
.then(function(res){ //*10
console.log("4-Put pizza on the table");
console.log(res);
return res; //*11
});
})
.then(function(res){ //*12
eatPizza(); //*13
console.log(res); //*14
});
ouput是:
1-Pay for Pizza
2-Put Pizza in microwave oven
Pizza
3-Could eat?
4-Put pizza on the table
hotPizza
5-Enjoy
hotPizza
这段代码非常有趣,而且涉及到很多的只是,我们一点一点看看。
首先,
1. orderPizza()(*0)返回Promise(*1)并且在函数体内已经注册了一个.then()(*2), 这个.then()返回了一个字符类型(*2.1)。
2. heatPizza()(*3)返回了Promise(*4)并且在函数体内注册了一个.then()(*5),这个.then()返回了一个字符类型(*6)。
3. 调用代码在外层注册了两个.then()(*8和*12), 在第一个.then()函数的内部返回了Promise(*9), 并且注册了一个.then()(*10), 在这个.then()函数体内返回了一个字符类型(*11)。外层的第二个.then()函数内部调用了同步函数eatPizza()(*13和*7)。
根据output我们看一遍执行顺序:
异步函数orderPizza()调用(*7.1)->
orderPizza()函数体内注册的.then()(*2)被调用 ->
外部注册的第一个.then()(*8)被调用 ->
上一个被调用的.then()(*8)返回了一个Promise(*9),于是上一个异步的回调被挂起(suspend),开始执行本次返回的异步函数heatPizza()(*9) ->
heatPizza()函数体内注册的.then()(*5)被调用 ->
外部注册的.then()(*10)被调用 ->
最后一个.then()被调用
是不是很复杂,其实只要记住我们前面提到的几个知识点也并不是很难理解,复习下:
1. .then()(称之为handler)会隐含返回一个Promise
2. 如果handler(in .then())显式return了一个值,这个值会作为隐含返回的Promise的结果,就是callback函数的参数(红色底色)
3. 一般来讲,上一个.then()返回的值会立刻传给下一个.then(), 但是有一个例外: 返回的不是一般的值,是个Promise,这种情况下程序就会suspend,等待最近返回的Promise完成才能继续并且这个Promise执行的结果会作为接下来的.then() callback的输入参数
另外一个值得关注的是值得传递.
1. 在.then()中返回的普通值会作为下一个.then()的输入参数。参看(*8.1)print出来的值来自(*2.1),即上一个.then()
2. 在.then()中返回的Promise,它的执行结果会作为接下来.then()的输入参数。参看(*14)实际来源于(*6)然后是(*11)
到这里,该将的知识点就完成了,有两个典型的问题供有兴趣的同学研究:
1. 为啥函数内部注册的.then()先于外面注册的.then()执行呢?
2. 如果在.then()中没有把Promise 返回会怎样?