jQuery Deferred / Promise
現今的網頁開發越來越複雜了,同時也有更多異步 (asynchronous) 的操作 (像是 Ajax),對於異步,通常的做法是用 callback,當事情完成後調用這些 callback 執行後續動作。但 callback 太多層或同時要等待多個異步事件時,會讓程式碼很亂難以管理也容易出錯 (callback hell)。
callback 太多層?像這樣...
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
jQuery 提供了 Deferred 來更好的處理這些異步問題,defer 字面上的意思就是延遲,而 deferred object 就是延遲到未來某個時間點再執行的物件。
建立 Deferred 物件 - jQuery.Deferred()
var dfd = $.Deferred();
deferred.done(), deferred.fail(), deferred.always()
當 deferred object 處理完成且成功時,會執行透過 deferred.done() 註冊的 callback;當 defer object 處理失敗時,會執行透過 deferred.fail() 註冊的 callback;而不管成功或失敗,都會執行透過 deferred.always() 註冊的 callback。
var dfd = $.Deferred();
dfd.done(function() {
alert('成功了');
}).fail(function() { // 串接
alert('失敗了');
});
// 隨時可以用 deferred object 註冊新的 callback
dfd.always(function() {
alert('不管成功或失敗');
});
deferred.resolve(), deferred.reject()
但怎麼完成一個 deferred object,然後執行對應的 callback functions?
deferred 物件有三種執行狀態 - 未完成、已完成、已失敗。我們可以用 resolve 來改變執行狀態為成功;用 reject 來改變執行狀態為失敗。
deferred.resolve() 結束 deferred object 的執行狀態 (成功),並執行 doneCallbacks, alwaysCallbacks。
deferred.reject() 結束 deferred object 的執行狀態 (失敗),並執行 failCallbacks, alwaysCallbacks。
var dfd = $.Deferred();
dfd.done(function() {
alert('你點了成功按鈕');
});
dfd.fail(function() {
alert('你點了失敗按鈕');
});
$('button.success').on('click', function() {
// 通知成功
dfd.resolve();
});
$('button.fail').on('click', function() {
// 通知失敗
dfd.reject();
});
resolve() 和 reject() 方法還可以接受一個參數,用來傳入 callback function。
var dfd = $.Deferred();
dfd.done(function(name) {
alert('Your name is ' + name);
});
$('button').on('click', function() {
dfd.resolve('Mike');
});
deferred.then(doneCallbacks, failCallbacks)
有時為了省事,我們可以用 .then() 來將 .done() 和 .fail() 合在一起寫。
dfd.then(
function() {
alert('succeeded');
}, function() {
alert('failed!');
}
);
deferred.state()
用 deferred.state() 來取得目前的執行狀態,有三種返回值:
- "pending": 未完成
- "resolved" : 已完成
- "rejected": 已失敗
jQuery.when(deferreds)
.when() 讓你可以為多個 Deferred 事件指定一個 callback,等所有的異步事件都結束後,再執行這個 callback。
var d1 = $.Deferred();
var d2 = $.Deferred();
var d3 = $.Deferred();
$.when(d1, d2, d3).done(function (v1, v2, v3) {
console.log(v1); // v1 is undefined
console.log(v2); // v2 is "abc"
console.log(v3); // v3 is an array [1, 2, 3, 4, 5]
});
d1.resolve();
d2.resolve('abc');
d3.resolve(1, 2, 3, 4, 5);
deferred.promise()
Promise 和 Deferred 是很類似的東西,除了 Promise object 少了改變狀態的方法 - .resolve(), .reject()。好處是什麼?如果你有 function 需要返回 deferred object,但你又不想讓其他的程式亂改狀態,你可以改成返回 promise!
function asyncEvent() {
var dfd = jQuery.Deferred();
// 亂數幾秒後 resolve 狀態
setTimeout(function() {
dfd.resolve('hurray');
}, Math.floor(400 + Math.random() * 2000));
// 亂數幾秒後 reject 狀態
setTimeout(function() {
dfd.reject('sorry');
}, Math.floor(400 + Math.random() * 2000));
// 返回一個 promise 避免被亂搞狀態
return dfd.promise();
}
// Attach a done, fail, and progress handler for the asyncEvent
$.when( asyncEvent() ).then(
function(status) {
alert(status + ', things are going well');
},
function( status ) {
alert(status + ', you fail this time');
}
);