when.js很小,壓縮后只有數kb,gzip后的大小幾乎可以忽略。在Node和瀏覽器環境里都可以使用when.js
首先,我們看一小段代碼:
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)顯示圖片,其中,任務1和2是異步,3是同步,使用的是最常見的callback機制來處理異步邏輯,好處是淺顯易懂,缺點是強耦合、不直觀、處理異常麻煩等等。
我們嘗試用when.js改寫下這段代碼:
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多了些什么:
var deferred = when.defer();
定義了一個deferred對象。
deferred.resolve(data);
在異步獲取數據完成時,把數據作為參數,調用deferred對象的resolve方法。
return deferred.promise;
返回了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的用法:
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 + '%';
}
);
then會傳遞錯誤,也就是說有多個任務串行執行時,我們可以只在最后一個then定義onRejected。只定義了onRejected的then等同於otherwise,也就是說 otherwise(onRejected) 是 then(undefined, onRejected) 的簡便寫法。
then會在try..catch..的包裹之下執行任務,所以任務的異常都會被when.js捕獲,當做失敗狀態處理,類似這樣:
try {
...
} catch (e) {
deferred.reject(e);
}
在任務狀態改變之后再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);
如果我們只是想把一個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"}] */
});
有時候,我們需要引入任務競爭機制,例如從一批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