轉:JavaScript Promises相當酷:一種有趣的方案庫

來源:互聯網
上載者:User

標籤:

許多的語言,為了將非同步模式處理得更像平常的順序,都包含一種有趣的方案庫,它們被稱之為promises,deferreds,或者futures。JavaScript的promises ,可以促進關注點分離,以代替緊密耦合的介面。 本文講的是基於Promises/A 標準的JavaScript promises。[http://wiki.commonjs.org/wiki/Promises/A]Promise的用例: 

  • 執行規則 

  • 多個遠程驗證 

  • 逾時處理 

  • 遠端資料請求 

  • 動畫 

  • 將事件邏輯從應用邏輯中解耦 

  • 消除回呼函數的恐怖三角

  • 控制並行的非同步作業

JavaScript promise是一個承諾將在未來傳回值的對象。是具有良好定義的行為的資料對象。promise有三種可能的狀態:

  1. Pending(待定)

  2. Rejected(拒絕)

  3. Resolved(已完成)

一個已經拒絕或者完成的承諾屬於已經解決的。一個承諾只能從待定狀態變成已經解決的狀態。之後,承諾的狀態就不變了。承諾可以在它對應的處理完成之後很久還存在。也就是說,我們可以多次取得處理結果。我們通過調用promise.then()來取得結果,這個函數一直到承諾對應的處理結束才會返回。我們可以靈活的串聯起一堆承諾。這些串聯起來的“then”函數應該返回一個新的承諾或者最早的那個承諾。 

通過這個樣式,我們可以像寫同步代碼一樣來寫非同步代碼。主要是通過組合承諾來實現: 

  • 堆棧式任務:多處散落在代碼中的,對應同一個承諾。
  • 並行任務:多個承諾返回同一個承諾。
  • 串列任務:一個承諾,然後接著執行另一個承諾。
  • 上面幾種的組合。

為什麼要這麼麻煩?只用基本的回呼函數不行嗎?

回呼函數的問題

回呼函數適合簡單的重複性事件,例如根據點擊來讓一個表單有效,或者儲存一個REST調用的結果。回呼函數還會使代碼形成一個鏈,一個回呼函數調用一個REST函數,並為REST函數設定一個新的回呼函數,這個新的回呼函數再調用另一個REST函數,依此類推。這就形成了一個1中的毀滅金字塔。代碼的橫向增長大於縱向的增長。回呼函數看起來很簡單,直到我們需要一個結果,而且是立刻就要,馬上就用在下一行的計算中。 

 

圖1:毀滅金字塔 

  1. ‘use strict‘; 
  2. var i = 0; 
  3. function log(data) {console.log(‘%d %s‘, ++i, data); }; 
  4.   
  5. function validate() { 
  6.    log("Wait for it ..."); 
  7.    // Sequence of four Long-running async activities 
  8.    setTimeout(function () { 
  9.       log(‘result first‘); 
  10.       setTimeout(function () { 
  11.          log(‘result second‘); 
  12.          setTimeout(function () { 
  13.             log(‘result third‘); 
  14.             setTimeout(function () { 
  15.                log(‘result fourth‘) 
  16.             }, 1000); 
  17.          }, 1000); 
  18.       }, 1000); 
  19.    }, 1000); 
  20.   
  21. }; 
  22. validate(); 

在圖1中,我使用timeout來類比非同步作業。管理異常的方法是痛苦的,很容易玩漏下遊行為。當我們編寫回調,那麼程式碼群組織變得混亂。圖2顯示了一個類比驗證流可以運行在NodeJS REPL。在下一節,我們將從pyramid-of-doom模式遷移到一個連續的promise。

Figure

  1. ‘use strict‘; 
  2. var i = 0; 
  3. function log(data) {console.log(‘%d %s‘, ++i, data); }; 
  4.   
  5. // Asynchronous fn executes a callback result fn 
  6. function async(arg, callBack) { 
  7.    setTimeout(function(){ 
  8.       log(‘result ‘ + arg); 
  9.       callBack(); 
  10.    }, 1000); 
  11. }; 
  12.   
  13. function validate() { 
  14.    log("Wait for it ..."); 
  15.    // Sequence of four Long-running async activities 
  16.    async(‘first‘, function () { 
  17.       async(‘second‘,function () { 
  18.          async(‘third‘, function () { 
  19.             async(‘fourth‘, function () {}); 
  20.          }); 
  21.       }); 
  22.    }); 
  23. }; 
  24. validate(); 

在NodeJS REPL執行的結果

  1. $ node scripts/examp2b.js 
  2. 1 Wait for it ... 
  3. 2 result first 
  4. 3 result second 
  5. 4 result third 
  6. 5 result fourth 

我曾經遇到一個AngularJS動態驗證的情況,根據對應表的值,動態限制表單項的值。限制項的有效值範圍被定義在REST服務上。 

我寫了一個調度器,根據請求的值,去操作函數棧,以避免回調嵌套。調度器從棧中彈出函數並執行。函數的回調會在結束時重新調用調度器,直到棧被清空。每次回調都記錄所有從遠程驗證調用返回的驗證錯誤。

我認為我寫的玩意兒是一種反模式。如果我用Angular的$http調用提供的promise,在整個驗證過程中我的思維會更近似線性形式,就像同步編程。平展的promise鏈是可讀的。繼續...

使用Promises

圖3顯示了我將驗證改寫成promise鏈的樣子。其中採用了kew promise庫。Q庫同樣適用。要使用該庫,首先使用npm將kew庫匯入到NodeJS,然後載入代碼到NodeJS REPL。

Figure

  1. ‘use strict‘; 
  2. var Q = require(‘kew‘); 
  3. var i = 0; 
  4.   
  5. function log(data) {console.log(‘%d %s‘, ++i, data); }; 
  6.   
  7. // Asynchronous fn returns a promise 
  8. function async(arg) { 
  9.     var deferred = Q.defer(); 
  10.     setTimeout(function () { 
  11.         deferred.resolve(‘result ‘ + arg);\ 
  12.     }, 1000); 
  13.     return deferred.promise; 
  14. }; 
  15.   
  16. // Flattened promise chain 
  17. function validate() { 
  18.     log("Wait for it ..."); 
  19.     async(‘first‘).then(function(resp){ 
  20.         log(resp); 
  21.         return async(‘second‘); 
  22.     }) 
  23.     .then(function(resp){ 
  24.         log(resp); 
  25.         return async(‘third‘) 
  26.     }) 
  27.     .then(function(resp){ 
  28.         log(resp); 
  29.         return async(‘fourth‘); 
  30.     }) 
  31.     .then(function(resp){ 
  32.         log(resp); 
  33.     }).fail(log); 
  34. }; 
  35. validate(); 

輸出和使用嵌套回調時相同:

  1. $ node scripts/examp2-pflat.js 
  2. 1 Wait for it ... 
  3. 2 result first 
  4. 3 result second 
  5. 4 result third 
  6. 5 result fourth 

該代碼稍微“長高”了,但我認為更易於理解和修改。更易於加上適當的錯誤處理。在鏈的末尾調用fail用於捕獲鏈中錯誤,但我也可以在任何一個then裡面提供一個reject的處理函數做相應的處理。

 

伺服器 或 瀏覽器

Promises在瀏覽器中就像在NodeJS伺服器中一樣有效。下面的地址, http://jsfiddle.net/mauget/DnQDx/,指向JSFiddle的一個展示如何使用一個promise的web頁面。 JSFiddle所有的代碼是可修改的。瀏覽器輸出的一個變化4所示。我故意操作隨意動作。你可以試幾次得到相反的結果。它是可以直接擴充到多個promise鏈, 就像前面NodeJS例子。

圖4.單個promise

並行 Promises

考慮一個非同步作業餵養另一個非同步作業。讓後者包括三個並行非同步行為,反過來,喂最後一個行動。只有當所有平行的子請求通過才能通過。5所示。這是靈感來自偶遇一打MongoDB操作。有些是合格的並行操作。我實現了promises的流流程圖。

圖5:非同步作業的結構

我們怎麼會類比那些在該圖中心行的並行promises?關鍵是,最大的promise庫有一個全功能,它產生一個包含一組子promises的父promie。當所有的子promises通過,父promise通過。如果有一個子promise拒絕,父promise拒絕。

圖6顯示了一個程式碼片段,讓十個並行的promises每個都包含一個文字promise。只有當十個子類通過或如果任何子類拒絕,最後的then方法才能完成。

  1. var promiseVals = [‘To ‘, ‘be, ‘, ‘or ‘, 
  2.     ‘not ‘, ‘to ‘, ‘be, ‘, ‘that ‘, 
  3.     ‘is ‘, ‘the ‘, ‘question.‘]; 
  4.   
  5. var startParallelActions = function (){ 
  6.     var promises = []; 
  7.   
  8.     // Make an asynchronous action from each literal 
  9.     promiseVals.forEach(function(value){ 
  10.         promises.push(makeAPromise(value)); 
  11.     }); 
  12.   
  13.     // Consolidate all promises into a promise of promises 
  14.     return Q.all(promises); 
  15. }; 
  16.   
  17. startParallelActions ().then( . . . 

下面的地址, http://jsfiddle.net/mauget/XKCy2/,針對JSFiddle在瀏覽器中運行十個並行promises,隨機的拒絕或通過。這裡有完整的代碼用於檢查和變化if條件。重新運行,直到你得到一個相反的完成。圖7顯示了積極的結果。

圖7:JSFiddle並行promises範例

孕育 Promise

許多api返回的promise都有一個then函數——他們是thenable。通常我只是通過then處理thenable函數的結果。然而,$q,mpromise,和kew庫擁有同樣的API用於建立,拒絕,或者通過promise。這裡有API文檔連結到每個庫的引用部分。我通常不需要構造一個promise,除了本文中的封裝promise的未知描述和timeout函數。請參考哪些我建立的promises。

Promise庫互操作

大多數JavaScript promise庫在then層級進行互操作。你可以從一個外部的promise建立一個promise,因為promise可以封裝任何類型的值。then可以支援跨庫工作。除了then,其他的promise函數則可能不同。如果你需要一個你的庫不包含的函數,你可以將一個基於你的庫的promise封裝到一個新的,基於含有你所需函數的庫建立的promise裡面。例如,JQuery的promise有時為人所詬病。那麼你可以將其封裝到Q,$q,mpromise,或者kew庫的promise中進行操作。

結語

現在我寫了這篇文章,而一年前我卻是猶豫要不要擁抱promise的那個。我只是單純地想完成一項工作。 我不想學習新的API,或是打破我原來的代碼(因為誤解了promise)。我曾經如此錯誤地認為!當我下了一點注時,就輕易就贏得了可喜的成果。

在這篇文章中,我已經簡單給出了一個單一的promise,promise鏈,和一個並行的promise的promise的的例子。 Promises不難使用。如果我可以使用它們,任何人都可以。 要查看完整的概念,我支援你點擊專家寫的參考指南。從Promises/A 的參考開始,從事實上的標準JavaScript的Promise 開始。

如果你還沒有直接使用的promise,試一下。下定決心:你會有一個不錯的體驗。我保證!

– Lou Mauget, [email protected]

參考連結

  • http://wiki.commonjs.org/wiki/Promises/A

  • https://github.com/bellbind/using-promise-q/

  • https://github.com/Medium/kew

  • https://docs.angularjs.org/api/ng/service/$q

  • https://github.com/aheckmann/mpromise

  • http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt2-practical-use

  • https://gist.github.com/domenic/3889970

  • http://sitr.us/2012/07/31/promise-pipelines-in-javascript.html

  • http://dailyjs.com/2014/02/20/promises-in-detail/

  • https://www.promisejs.org/

  • http://solutionoptimist.com/2013/12/27/javascript-promise-chains-2/

  • http://www.erights.org/elib/distrib/pipeline.html

  • http://zeroturnaround.com/rebellabs/monadic-futures-in-java8/

轉:JavaScript Promises相當酷:一種有趣的方案庫

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.