ES6標準出爐以前,一個幽靈,回調的幽靈,遊蕩在JavaScript世界。前端
正所謂:node
世界本沒有回調,寫的人多了,也就有了
})})})})})
。git
Promise
的興起,是由於異步方法調用中,每每會出現回調函數一環扣一環的狀況。這種狀況致使了回調金字塔問題的出現。不只代碼寫起來費勁又不美觀,並且問題複雜的時候,閱讀代碼的人也難以理解。
舉例以下:程序員
db.save(data, function(data){ // do something... db.save(data1, function(data){ // do something... db.save(data2, function(data){ // do something... done(data3); // 返回數據 }) }); });
假設有一個數據庫保存操做,一次請求須要在三個表中保存三次數據。那麼咱們的代碼就跟上面的代碼類似了。這時候假設在第二個db.save
出了問題怎麼辦?基於這個考慮,咱們又須要在每一層回調中使用相似try...catch
這樣的邏輯。這個就是萬惡的來源,也是node剛開始廣爲詬病的一點。github
另一個缺點就是,假設咱們的三次保存之間並無先後依賴關係,咱們仍然須要等待前面的函數執行完畢, 才能執行下一步,而沒法三個保存並行,以後返回一個三個保存事後須要的結果。(或者說實現起來須要技巧)數據庫
不幸的是,在我剛開始接觸node的時候,我寫了大量這樣的hell。segmentfault
做爲一個有時還動下腦子的程序員,我嘗試了樸靈大人的eventproxy。後來由於仍是寫前端代碼多一些,我接觸了ES6,發現了一個解決回調深淵的利器Promise
。數組
其實早在ES6的Promise
以前,Q
,when.js
,bluebird
等等庫早就根據Promise
標準(參考Promise/A+)造出了本身的promise
輪子。
(看過一篇文章,我以爲頗有道理。裏面說,不要擴展內置的原生對象。這種作法是不能面向將來的。因此這裏有一個提示:使用擴展原生Promise
的庫時,須要謹慎。)promise
這裏僅討論原生的Promise
。併發
在詳解Promise
以前,先來點理論:
Promise/A+
規範, 規定Promise對象是一個有限狀態機。它三個狀態:
pending
(執行中)
fulfilled
(成功)
reject
(拒絕)
其中pending爲初始狀態,fulfilled和rejected爲結束狀態(結束狀態表示promise的生命週期已結束)。
狀態轉換關係爲:
pending->fulfilled,pending->rejected。
隨着狀態的轉換將觸發各類事件(如執行成功事件、執行失敗事件等)。
Promise的長相就像這樣子:
var promise = new Promise(function func(resolve, reject){ // do somthing, maybe async if (success){ return resolve(data); } else { return reject(data); } }); promise.then(function(data){ // do something... e.g console.log(data); }, function(err){ // deal the err. })
這裏的變量promise
是Promise
這個對象的實例。
promise對象在建立的時候會執行func
函數中的邏輯。
邏輯處理完畢而且沒有錯誤時,resolve
這個回調會將值傳遞到一個特殊的地方。這個特殊的地方在哪呢?就是下面代碼中的then
,咱們使用then
中的回調函數來處理resolve後的結果。好比上面的代碼中,咱們將值簡單的輸出到控制檯。若是有錯誤,則reject
到then
的第二個回調函數中,對錯誤進行處理。
配合上面的有限狀態機的理論,咱們知道在Promise
構造函數中執行回調函數代碼時,狀態爲pending
,resolve
以後狀態爲fulfilled
,reject
以後狀態爲reject
以上是promise的第一次數據流動狀況。
比較funny的是,promise的then
方法依然可以返回一個Promise
對象,這樣咱們就又能用下一個then
來作同樣的處理。
第一個then
中的兩個回調函數決定第一個then
返回的是一個什麼樣的Promise
對象。
假設第一個then
的第一個回調沒有返回一個Promise
對象,那麼第二個then
的調用者仍是原來的Promise對象,只不過其resolve
的值變成了第一個then
中第一個回調函數的返回值。
假設第一個then
的第一個回調函數返回了一個Promise
對象,那麼第二個then
的調用者變成了這個新的Promise
對象,第二個then
等待這個新的Promise
對象resolve
或者reject
以後執行回調。
話雖然饒了一點,可是我自我感受說的仍是很清楚的呢。哈哈~
若是任意地方遇到了錯誤,則錯誤以後交給遇到的第一個帶第二個回調函數的then
的第二個回調函數來處理。能夠理解爲錯誤一直向後reject
, 直到被處理爲止。
另外,Promise
對象還有一個方法catch
,這個方法接受一個回調函數來處理錯誤。即:
promise.catch(function(err){ // deal the err. })
假設對錯誤的處理是類似的,這個方法能夠對錯誤進行集中統一處理。因此其餘的then
方法就不須要第二個回調啦~
Promise有一個"靜態方法"——Promise.all
(注意並不是是promise.prototype
), 這個方法接受一個元素是Promise對象的數組。
這個方法也返回一個Promise
對象,若是數組中全部的Promise
對象都resolve了,那麼這些resolve的值將做爲一個數組做爲Promise.all
這個方法的返回值的(Promise
對象)的resolve值,以後能夠被then
方法處理。若是數組中任意的Promise
被reject
,那麼該reject
的值就是Promise.all
方法的返回值的reject
值.
很op的一點是:
then方法的第一個回調函數接收的resolve值(如上所述,是一個數組)的順序和Promise.all中參數數組的順序一致,而不是按時間順序排序。
還有一個和Promise.all
相相似的方法Promise.race
,它一樣接收一個數組,只不過它只接受第一個被resolve的值。
Promise.resovle
方法,能夠將不是Promise對象做爲參數,返回一個Promise
對象。
有兩種情形:
假設傳入的參數沒有一個.then
方法,那麼這個返回的Promise
對象變成了resolve狀態,其resolve的值就是這個對象自己。
假設傳入的參數帶有一個then
方法(稱爲thenable
對象), 那麼將這個對象的類型變爲Promise
,其then
方法變成Promise.prototype.then
方法。
最後說一點很重要的事:Promise
的做用是解決回調金字塔的問題,對於控制異步流程實際上沒有起到很大的做用。真正使用Promise
對異步流程進行控制,咱們還要藉助ES6 generator
函數。(例如Tj大神的co
庫的實現)。
然而ES7將有一個更加牛逼的解決方案:async/await
,這個方案相似於co
,可是加了原生支持。拭目以待吧。
以上。一點微小的看法,謝謝你們。