異步編程when.js

when.js很小,壓縮後只有數kb,gzip後的大小几乎能夠忽略。在Node和瀏覽器環境裏均可以使用when.jsjavascript

首先,咱們看一小段代碼:html

var getData = function(callback) { $.getJSON(api, function(data){ callback(data[0]); }); } var getImg = function(src, callback) { var img = new Image(); img.onload = function() { callback(img); }; img.src = src; } var showImg = function(img) { $(img).appendTo($('#container')); } getData(function(data) { getImg(data, function(img) { showImg(img); }); });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

這段代碼完成了三個任務:1)獲取數據;2)加載圖片;3)顯示圖片,其中,任務1和2是異步,3是同步,使用的是最多見的callback機制來處理異步邏輯,好處是淺顯易懂,缺點是強耦合、不直觀、處理異常麻煩等等。java

咱們嘗試用when.js改寫下這段代碼:git

var getData = function() { var deferred = when.defer(); $.getJSON(api, function(data){ deferred.resolve(data[0]); }); return deferred.promise; } var getImg = function(src) { var deferred = when.defer(); var img = new Image(); img.onload = function() { deferred.resolve(img); }; img.src = src; return deferred.promise; } var showImg = function(img) { $(img).appendTo($('#container')); } getData() .then(getImg) .then(showImg);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

看最後三行代碼,是否是一目瞭然,很是的語義化?來看下改寫後的任務一、2多了些什麼:github

var deferred = when.defer();
  • 1

定義了一個deferred對象。編程

deferred.resolve(data);
  • 1

在異步獲取數據完成時,把數據做爲參數,調用deferred對象的resolve方法。api

return deferred.promise;
  • 1

返回了deferred對象的promise屬性。 
在Promises/A規範中,每一個任務都有三種狀態:默認(pending)、完成(fulfilled)、失敗(rejected)。數組

  • 默認狀態能夠單向轉移到完成狀態,這個過程叫resolve,對應的方法是deferred.resolve(promiseOrValue);
  • 默認狀態還能夠單向轉移到失敗狀態,這個過程叫reject,對應的方法是deferred.reject(reason);
  • 默認狀態時,還能夠經過deferred.notify(update)來宣告任務執行信息,如執行進度;
  • 狀態的轉移是一次性的,一旦任務由初始的pending轉爲其餘狀態,就會進入到下一個任務的執行過程當中。

有人可能會以爲奇怪:改變任務狀態的resolve和reject方法是定義在deferred對象上,但最後返回的倒是deferred的promise屬性。這麼作一是由於規範就是這麼定的,二是能夠防止任務狀態被外部改變。 
then有三個參數,分別是onFulfilled、onRejected、onProgress,經過這三個參數,就能夠指定上一個任務在resolve、reject和notify時該如何處理。例如上一個任務被resolve(data),onFulfilled函數就會被觸發,data做爲它的參數;被reject(reason),那麼onRejected就會被觸發,收到reason。任什麼時候候,onFulfilled和onRejected都只有其一能夠被觸發,而且只觸發一次;onProgress顧名思義,每次notify時都會被調用。下面是reject和notify的用法:promise

function run() { var deferred = when.defer(); var start = 1, end = 100; (function() { if(start <= end) { deferred.notify(start++); setTimeout(arguments.callee, 50); } else { deferred.reject('time out!'); } })(); return deferred.promise; } run().then(undefined, function(reason) { alert(reason); }, function(s) { document.getElementById('output').innerHTML = s + '%'; } );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

then會傳遞錯誤,也就是說有多個任務串行執行時,咱們能夠只在最後一個then定義onRejected。只定義了onRejected的then等同於otherwise,也就是說 otherwise(onRejected) 是 then(undefined, onRejected) 的簡便寫法。 
then會在try..catch..的包裹之下執行任務,因此任務的異常都會被when.js捕獲,當作失敗狀態處理,相似這樣:瀏覽器

try { ... } catch (e) { deferred.reject(e); }
  • 1
  • 2
  • 3
  • 4
  • 5

在任務狀態改變以後再then,依然能夠正常工做,後續任務會馬上執行。若是要在多個任務最後作cleanup工做,而無論以前的任務成功與否,能夠用ensure方法。它只接受一個參數onFulfilledOrRejected,始終會執行。另外when.js還有一個always方法,即將廢棄,建議你們不要使用。 
回到上面加載圖片的場景,若是把任務2變爲:加載多張圖片,所有完成後再執行任務3。這時候須要用到when.all,when.all接受一個promise數組,返回promise,這個promise會在promise數組中每個promise都resolve以後再resolve。提及來拗口,看代碼就明白了:

var getData = function() { var deferred = when.defer(); $.getJSON(api, function(data){ var data = data.slice(0, 3); deferred.resolve(data); }); return deferred.promise; } var getImg = function(src) { var deferred = when.defer(); var img = new Image(); img.onload = function() { deferred.resolve(img); }; img.src = src; return deferred.promise; } var showImgs = function(imgs) { $(imgs).appendTo($('#container')); } var getImgs = function(data) { var deferreds = []; for(var i = 0; i < data.length; i++) { deferreds.push(getImg(data[i])); } return deferreds; } when.all(getData().then(getImgs)).then(showImgs);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

若是咱們只是想把一個promise數組挨個執行一遍,能夠用when.settle:

var promise1 = function() { var deferred = when.defer(); setTimeout(function() { deferred.reject('A'); }, 2000); return deferred.promise; }; var promise2 = function() { var deferred = when.defer(); setTimeout(function() { deferred.resolve('B'); }, 2000); return deferred.promise; }; when.settle([promise1(), promise2()]).then(function(result) { console.log(result); /* [{"state":"rejected","reason":"A"}, {"state":"fulfilled","value":"B"}] */ });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

有時候,咱們須要引入任務競爭機制,例如從一批cdn中找到最快的那個,when.any就派上用場了,when.any接受promise數組,在其中任何一個resolve後就接着執行後續任務了。若是要在一批promise中某幾個resolve後執行後續任務,能夠用when.some,它比when.any多一個howMany的參數。 
Promise給異步編程代碼帶來了巨大的方便,今後咱們能夠更專一單個任務的實現,promise會很好的替咱們解決任務調度問題。when.js提供的功能遠遠不止本文提到的這些,有興趣的同窗能夠前往官方api文檔瞭解更多。

github:https://github.com/cujojs/when#legacy-environments 
via:Jerry Qu 
本文連接:https://imququ.com/post/promises-when-js.html

相關文章
相關標籤/搜索