自从ES6流行起来,Promise 的使用变得更频繁更普遍了,好比异步请求通常返回一个 Promise 对象,Generator 中 yield 后面通常跟 Promise 对象,ES7中 Async 函数中 await 后面通常也是 Promise 对象,还有更多的 NodeAPI 也会返回 Promise 对象,能够说如今的编程中 Promise 的使用无处不在,那么咱们是否真的弄懂了 Promise 呢?是否有误用或错误使用 Promise 呢?是否知道 Promise 的实现原理和 Promise 的花样玩法呢?下面让咱们一块儿来探讨一下吧。git
这里只列举规范中的大体内容,详细内容请查看 Promises/A+ 中文 ,这是ES6 Promises的前身,是一个社区规范,它和 ES6 Promises 有不少共通的内容。es6
Promise
的初始状态是 Pending
,状态只能被转换为(Resolved)Fulfilled
或Rejected
,状态的转换不可逆。then
方法,接收两个可选函数参数onFulfilled
、onRejected
,then
方法必须返回一个新的 Promise
对象,为了保证 then
中回调的执行顺序,回调必须使用异步执行。Promise
的实现必须能够互相调用具体标准的实现将在 中篇 - 手动封装 中详细说明github
若是你对 Promise的使用 还不是很了解,可参考阅读如下资料:ajax
这里只对ES6 Promise API作简要说明编程
.then(null, rejectFn)
的语法糖,返回值也是一个 新的Promise对象.catch()
捕获then
和 catch
返回的都是 Promise
对象,因此才能够不断的链式调用all()
的返回值也是新的Promise对象Promise.race()
race()
的返回值也是新的Promise对象只须要在浏览器中加载Polyfill类库,就能使用IE10等或者尚未提供对Promise支持的浏览器中使用Promise里规定的方法。json
calvinmetcalf/lie 很是简洁的 promise 库,中篇中的手动封装实现就是参考了这个库
jakearchibald/es6-promise 兼容 Promises/A+ 的类库, 它只是 RSVP.js 的一个子集,只实现了Promises 规定的 API。
yahoo/ypromise 这是一个独立版本的 YUI 的 Promise Polyfill,具备和 ES6 Promises 的兼容性segmentfault
Promise扩展类库除了实现了Promise中定义的规范以外,还增长了本身独自定义的功能。api
kriskowal/q 类库 Q 实现了 Promises 和 Deferreds 等规范。 它自2009年开始开发,还提供了面向Node.js的文件IO API Q-IO 等, 是一个在不少场景下都能用获得的类库。
petkaantonov/bluebird这个类库除了兼容 Promise 规范以外,还扩展了取消promise对象的运行,取得promise的运行进度,以及错误处理的扩展检测等很是丰富的功能,此外它在实现上还在性能问题下了很大的功夫。数组
注意
在项目中,有可能两个不一样的模块使用的是两个不一样的Promise类库,那么在大部分的Promise的实现中,都是遵循 Promise/A+ 标准和兼容ES6 Promise接口的,也是不一样的Promise的实现是能够互相调用的,如何调用,将在下面说明。promise
loadAsync1().then(function(data1) { loadAsync2(data1).then(function(data2) { loadAsync3(data2).then(okFn, failFn) }); });
Promise是用来解决异步嵌套回调的,这种写法虽然可行,但违背了Promise的设计初衷
改为下面的写法,会让结构更加清晰
loadAsync1() .then(function(data1) { return loadAsync2(data1) }) .then(function(data2){ return loadAsync3(data2) }) .then(okFn, failFn)
loadAsync1() .then(function(data1) { loadAsync2(data1) }) .then(function(data2){ loadAsync3(data2) }) .then(res=>console.log(res))
promise 的神奇之处在于让咱们可以在回调函数里面使用 return 和 throw, 因此在then中能够return出一个promise对象或普通的值,也能够throw出一个错误对象,但若是没有任何返回,将默认返回 undefined,那么后面的then中的回调参数接收到的将是undefined,而不是上一个then中内部函数 loadAsync2 执行的结果,后面都将是undefined。
loadAsync1() .then(function(data1) { return loadAsync2(data1) }) .then(function(data2){ return loadAsync3(data2) }) .then(okFn, failFn)
这里的调用,并无添加catch方法,那么若是中间某个环节发生错误,将不会被捕获,控制台将看不到任何错误,不利于调试查错,因此最好在最后添加catch方法用于捕获错误。
添加catch
loadAsync1() .then(function(data1) { return loadAsync2(data1) }) .then(function(data2){ return loadAsync3(data2) }) .then(okFn, failFn) .catch(err=>console.log(err))
在有些状况下catch与then(null, fn)并不等同,以下
ajaxLoad1() .then(res=>{ return ajaxLoad2() }) .catch(err=> console.log(err))
此时,catch捕获的并非ajaxLoad1的错误,而是ajaxLoad2的错误,因此有时候,二者仍是要结合起来使用:
ajaxLoad1() .then(res=>{ return ajaxLoad2() }, err=>console.log(err)) .catch(err=> console.log(err))
function loadAsyncFnX(){ return Promise.resolve(1); } function doSth(){ return 2; } function asyncFn(){ var promise = loadAsyncFnX() promise.then(function(){ return doSth(); }) return promise; } asyncFn().then(res=>console.log(res)).catch(err=>console.log(err)) // 1
上面这种用法,从执行结果来看,then中回调的参数其实并非doSth()返回的结果,而是loadAsyncFnX()返回的结果,catch 到的错误也是 loadAsyncFnX()中的错误,因此 doSth() 的结果和错误将不会被后而的then中的回调捕获到,造成了断链,由于 then 方法将返回一个新的Promise对象,而不是原来的Promise对象。
改写以下
function loadAsyncFnX(){ return Promise.resolve(1); } function doSth(){ return 2; } function asyncFn(){ var promise = loadAsyncFnX() return promise.then(function(){ return doSth(); }) } asyncFn().then(res=>console.log(res)).catch(err=>console.log(err)) // 2
new Promise(resolve=>resolve(8)) .then(1) .catch(null) .then(Promise.resolve(9)) .then(res=> console.log(res)) // 8
这里,若是then或catch接收的不是函数,那么就会发生穿透行为,因此在应用过程当中,应该保证then接收到的参数始终是一个函数。
并行执行
getAsyncArr() .then(promiseArr=>{ var resArr = []; promiseArr.forEach(v=>{ v().then(res=> resArr.push(res)) }) return resArr; }) .then(res=>console.log(res))
使用forEach遍历执行promise,在上面的实现中,第二个then有可能拿到的是空的结果或者不完整的结果,由于,第二个then的回调没法预知 promiseArr 中每个promise是否都执行完成,那么这里可使用 Promise.all 结合 map 方法去改善
getAsyncArr() .then(promiseArr=>{ return Promise.all(promiseArr); }) .then(res=>console.log(res))
若是须要串行执行,那和咱们能够利用数据的reduce来处理串行执行
var pA = [ function(){return new Promise(resolve=>resolve(1))}, function(data){return new Promise(resolve=>resolve(1+data))}, function(data){return new Promise(resolve=>resolve(1+data))} ] pA.reduce((prev, next)=>prev.then(next).then(res=>res),Promise.resolve()) .then(res=>console.log(res)) // 3
Promise.reoslve
有一个做用就是能够将 thenable
对象转换为 promise
对象。
thenable
对象,指的是一个具备 .then
方法的对象。
要求是 thenable
对象所拥有的 then
方法应该和 Promise
所拥有的 then
方法具备一样的功能和处理过程。
一个标准的 thenable 对象应该是这样的
1 var thenable = { 2 then: function(resolve, reject) { 3 resolve(42); 4 } 5 };
使用 Promise.resolve转换
Promise.resolve(thenable).then(function(value) { console.log(value); // 42 });
一样具备标准的thenable特性的是 不一样的实现Promise标准的类库,因此 ES6 Promise 与 Q 与buldbird 的对象都是能够互相转换的。
jQueyr的defer对象转换为ES6 Promise对象
Promise.resolve($.ajax('api/data.json')).then(res=>console.log(res)))
但也不是全部thenable对象都能被成功转换,主要看各类类库实现是否遵循 Promise/A+标准,不过此类使用场景并很少,不作深刻讨论。
then
方法中 永远 return
或 throw
promise
链中可能出现错误,必定添加 catch
then
方法promise
写成嵌套通过本篇的对Promise相关知识的理解和学习,基本上对Promise的概念和使用有了比较详细的了解,下一篇就让咱们一块儿进入 Promise 的源码世界看一看吧。