ES6 异步编程之二:Promise
异步回调的泥潭
异步回调是最直接的异步结果处理模式,将一个回调函数callback
扔进异步处理函数中,当异步处理获得结果之后再调用这个回调函数就可以继续之后的处理,但是如果这个callback
又是一个异步调用呢?众所周知的,在JavaScript中异步回调的层层嵌套几乎是反人性的,代码编写、修改和阅读对我等有代码洁癖的人而言是一种煎熬,这便是异步回调泥潭了。
今天对于处理异步调用已经有了很多成熟的方案,在我看来这些方案都无外乎在解决一个问题:“如何能看似顺序地传递异步调用的结果?”,本文要说的Promise
就是ES6原生提供的一个解决方案。
在对Promise
进行叙述之前,依旧引用阮大的《ECMAScript 6入门》一书中的Promise章节便于大家更严谨和全面的学习和参考。
Promise
承诺,即对未来的许诺,如果诺言实现,然后(then
)就如何如何……Promise
极其生动的讲述了一个言出必行的故事。
new Promise(function(resolve, reject){ //开始实现承诺 .... .... if(承诺兑现时) { resolve(dollars); //兑现承诺的结果是得到'一大笔美金' } else { reject('绝交'); //没兑现承诺就绝交 } }).then(function(dollars){ //然后有钱了,买房买车娶妻生子 let d1 = buyHouse(dollars); //把每次消费剩余的钱传给下一个函数 let d2 = buyCar(d1); let d3 = marry(d2); makeBaby(d3); }).catch(function(result){//然后如果绝交了,还是继续吃土 //继续吃土 }); console.log('故事开始....');
看过上面的这个俗不可耐的故事之后需要理解几件事情:
言出必行:一个
Promise
构造出来之后,构造时传入的异步函数就立即执行;*
注:因大凡使用promise都是在异步调用场景,下文所说的异步函数都是指构造promise时传入的函数*Promise
实例内部维护了一个状态机,状态变化只可能是pending
到resolved
或者pending
到rejected
;执行
resolve
:pending
变化到resolved
执行
reject
:pending
变化到rejected
抛出错误:
pending
变化到rejected
then
的第一个回调函数只会在发生了resolve
之后执行,本质上是在Promise
到达resolved
状态执行;then
的第二个回调函数或者catch
的回调函数会在发生reject
之后或者异步函数执行抛出错误时执行,本质上是在Promise
到达rejected
状态时执行;异步函数执行得到结果可以通过
resolve
或者reject
将结果传出;调用
resolve
传入的值会作为then
第一个回调函数的入参调用
reject
传入的值作为then
第二个回调函数或者catch
的回调函数的入参如果异步函数抛出了异常,异常会作为
then
第二个回调函数或者catch
的回调函数的入参
'故事开始....'会先输出,而不是等到
then
的回调函数执行完毕才输出,说明传入then
的回调函数是异步执行,同理catch
也是一样;
异步函数调用链
then
和catch
都是Promise
的实例方法,都返回一个新的Promise
,因此可以轻而易举地实现链式编程,比如上面的例子中“把每次消费剩余的钱”传给下一个函数可以改写成这样:
....//前面省略 .then(function(dollars){ return buyHouse(dollars); }).then(function(d1){ return buyCar(d1); }).then(function(d2){ return marry(d2); }).then(function(d3){ return makeBaby(d3); }).catch(function(result){ //继续吃土 });
看到这里你可能认为前一个then
回调函数的返回值是后一个then
的回调函数的入参,但这是不准确的,因为当then
回调函数返回的是个Promise
对象时,这个Promise
对象到终态时后一个then
才会执行,并且该Promise
对象执行resolve
时的入参才是后一个then
的回调函数入参;
此时有必要对Promise
的一个类方法resolve
做以下说明,它的特性两句话:
如果传入的是个
Promise
对象,则直接返回这个Promise
;如果是其他任何一个值(包括Error对象和undefined)则直接转换为一个
resolved
状态的Promise
对象;
比如说下面的代码:
//以下的p1和p2逻辑上等同 let p1 = Promise.resolve(1); let p2 = new Promise(function(resolve, reject) { resolve(1); }); //以下的p3和p4等同 let p3 = new Promise(function(r, j) {}); let p4 = Promise.resolve(p3); console.log(p3 == p4); //true console.log(p3 === p4); //true //以下三者逻辑上等同 Promise.resolve().then(function(dollars) { return 1 + 1; }).then(function(v) { console.log(v); }); Promise.resolve().then(function(dollars) { return new Promise(function(r, j) { r(1 + 1) }); }).then(function(v) { console.log(v); }); Promise.resolve().then(function(dollars) { return Promise.resolve(1 + 1); }).then(function(v) { console.log(v); });
我们可以利用Promise
异步执行结果传出的机制和then
的链式调用,将层层嵌套的函数调用变为通过then
顺序连接的链式调用
从写法和形式上看是不是人性很多呢?
通过Promise
实现的链式异步函数调用,以斐波那契数列举例如下:
//一个异步的斐波那契计算 function fibonacci(v) { return new Promise(function(resolve, reject) { //每一个异步调用都返回了一个Promise setTimeout(function() { console.log(`${v.a}`); [v.a, v.b] = [v.b, v.a + v.b]; resolve(v); }, 500); }); } //以下两者逻辑等同,每个then都等待上一个promise的结果形成一条链。 // fibonacci({ a: 0, b: 1 }) // .then(fibonacci) // .then(fibonacci) // .then(fibonacci) // .then(fibonacci) // .then(fibonacci) // .then(fibonacci); Promise.resolve() .then(() => fibonacci({ a: 0, b: 1 })) .then(fibonacci) .then(fibonacci) .then(fibonacci) .then(fibonacci) .then(fibonacci) .then(fibonacci);