自從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 的源碼世界看一看吧。