淺談ES6原生Promise

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以前,Qwhen.jsbluebird等等庫早就根據Promise標準(參考Promise/A+)造出了本身的promise輪子。
(看過一篇文章,我以爲頗有道理。裏面說,不要擴展內置的原生對象。這種作法是不能面向將來的。因此這裏有一個提示:使用擴展原生Promise的庫時,須要謹慎。)promise

這裏僅討論原生的Promise併發

ES6 Promise

Promise對象狀態

在詳解Promise以前,先來點理論:

Promise/A+規範, 規定Promise對象是一個有限狀態機。它三個狀態:

  • pending(執行中)

  • fulfilled(成功)

  • reject(拒絕)

其中pending爲初始狀態,fulfilled和rejected爲結束狀態(結束狀態表示promise的生命週期已結束)。

狀態轉換關係爲:

pending->fulfilled,pending->rejected。

隨着狀態的轉換將觸發各類事件(如執行成功事件、執行失敗事件等)。

Promise形式

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.
})

這裏的變量promisePromise這個對象的實例。

promise對象在建立的時候會執行func函數中的邏輯。

邏輯處理完畢而且沒有錯誤時,resolve這個回調會將值傳遞到一個特殊的地方。這個特殊的地方在哪呢?就是下面代碼中的then,咱們使用then中的回調函數來處理resolve後的結果。好比上面的代碼中,咱們將值簡單的輸出到控制檯。若是有錯誤,則rejectthen的第二個回調函數中,對錯誤進行處理。

配合上面的有限狀態機的理論,咱們知道在Promise構造函數中執行回調函數代碼時,狀態爲pendingresolve以後狀態爲fulfilledreject以後狀態爲reject

Promise數據流動

以上是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有一個"靜態方法"——Promise.all(注意並不是是promise.prototype), 這個方法接受一個元素是Promise對象的數組。

這個方法也返回一個Promise對象,若是數組中全部的Promise對象都resolve了,那麼這些resolve的值將做爲一個數組做爲Promise.all這個方法的返回值的(Promise對象)的resolve值,以後能夠被then方法處理。若是數組中任意的Promisereject,那麼該reject的值就是Promise.all方法的返回值的reject值.

很op的一點是:
then方法的第一個回調函數接收的resolve值(如上所述,是一個數組)的順序和Promise.all中參數數組的順序一致,而不是按時間順序排序。

還有一個和Promise.all相相似的方法Promise.race,它一樣接收一個數組,只不過它只接受第一個被resolve的值。

將其餘對象變爲Promise對象

Promise.resovle方法,能夠將不是Promise對象做爲參數,返回一個Promise對象。

有兩種情形:

  1. 假設傳入的參數沒有一個.then方法,那麼這個返回的Promise對象變成了resolve狀態,其resolve的值就是這個對象自己。

  2. 假設傳入的參數帶有一個then方法(稱爲thenable對象), 那麼將這個對象的類型變爲Promise,其then方法變成Promise.prototype.then方法。

Promise是解決異步的方案嗎?

最後說一點很重要的事:Promise的做用是解決回調金字塔的問題,對於控制異步流程實際上沒有起到很大的做用。真正使用Promise對異步流程進行控制,咱們還要藉助ES6 generator函數。(例如Tj大神co庫的實現)。

然而ES7將有一個更加牛逼的解決方案:async/await,這個方案相似於co,可是加了原生支持。拭目以待吧。

文檔

mozilla開發者文檔


以上。一點微小的看法,謝謝你們。

相關文章
相關標籤/搜索