[譯文]jQuery的Deferred
jQuery的$.Deferred()指令是該函式庫中近來最強大的指令。它並非是一個新觀念,但對於數以千計的前端開發人員來說是極有價值的。從核心來看,Deferred非常的簡單,但卻是一個很強大的非同步管理的工具。眾所周知開發前端時,經常需要進行非同步的處理。
我們把焦點放在Deferred以及jQuery在這方面所提供的API。下面有許多豐富的範例。讀完後,你將會瞭解到什麼是Deferred以及何時使用它。
瞭解核心概念
Deferred本質上就是一個Proxy物件,它可以套用到任何非同步處理: Ajax請求,動畫,或是Web Worker。使用者行為在設計上可以採用延遲處理;在頁面上的表單,Deferred允許你可以指定當發生錯誤或順利完成時要進行怎樣的處理。jQuery允許你註冊回呼函數,該函數會在Deferred判定成功時,或是發生錯誤,或是告知正處於某種狀態時被呼叫。
你可能已經使用過Deferred。jQuery的Ajax方法回傳的物件已經實做了Deferred介面。這個物件會解析Ajax請求是成功或是失敗。
有一個很重要你必須要知道的東西
Deferred抽象來看是讓開發人員避開非同步處理。Deferred可以無限延展,而回呼函數可以持續的在Deferred物件生命週期中添加。這種手法的關鍵在於Deferred的回呼函數會在Deferred解析完狀態之後立刻被呼叫。你不需要去擔心非同步的計算單元(例:Ajax請求)是否已完成。繫結到Deferred的回呼函數會在Deferred在當下或待會解析出執行狀態後被執行。
使用jQuery的Deferred
解析和拒絕
Deferred的核心方法就是你需要去處理Deferred物件的解析或拒絕。你可以使用$.Deferred()方法創建一個新的Deferred物件。該物件的done()方法會在Deferred被解析被執行,而fail()方法則是在Deferred物件被拒絕時執行。實際上,解析或拒絕一個Deferred物件將以藉由resolve()和reject()來達成。本質上,jQuery的ajax()方法會在請求成功時呼叫resolve()方法,而會在請求是錯誤(例: Http狀態為404)時呼叫reject()方法。
要記得,若你掛載done或fail處理到deferred物件上,且該物件已經解析過了,該函數會立馬被執行。
通知Deferreds: notify()和progress()
jQuery 1.7亦添加了Progress的概念到Deferred中,這個Progress是針對拒絕和解析用的。progress()允許你掛載Deferred呼叫notify()方法時的回呼函數。這讓Deferred得以回應"解析狀態的目前進度"。Deferred描述一個長時間資源叫用時,就可以在progress()函數上註冊一個回呼函數,並且該函數可以週期性的更新進度列,舉例來說,Deferred物件會在載入/載入完成後解析這些狀態時被通知目前進度。
回傳promis()
在許多案例中,假若你正回傳一個Deferred,但你並不想要讓這個物件被解析或拒絕-你想要自主控制。在這個情況下,你可以回傳promise物件。在jQuery的術語中,promise是一種唯讀型的Deferred物件。promise允許你掛載回呼函數並且詢問Deferred的狀態,但你無法要它改變它自己的狀態(例: 解析/拒絕)。jQuery的ajax()方法回傳的是promise,因為它自己內部有處理Ajax請求的判定是正確或錯誤。
藉由when()同步所有非同步事件
$.when()允許一或多個Deferred並且產生一個新的Deferred物件, 該物件只有在所有Deferred物件都解析後才會解析。這可以讓你去組合多個非同步事件成為一個。
思考下面的範例:
我們的UI請求資料來自於兩個獨立的Ajax請求,並且它需要這兩個請求都取得資料後才能繪出畫面。
沒有when(),我會將焦點放在巢狀式回呼函數來確保兩個請求已經完成。代價為何?我們需要在兩個不同的地方指定錯誤處理流程。
var name = $.post('/echo/json/', {json:JSON.stringify({'name': "Matt Baker"})});
var lastUpdate = $.post('/echo/json/', {json:JSON.stringify({'lastUpdate':"Hello World"})});
name.done(function(nameData) {
var name = nameData.name;
lastUpdate.done(function(lastUpdateData) {
var lastUpdate = lastUpdateData.lastUpdate;
$("#render-me").html(name + " 's last update was: " + lastUpdate);
}).fail(function() {
$("#error").html("an error occured").show();
}).fail(function() {
$("#error").html("an error occured").show();
});
});
我們可以使用$.when()來組合兩個Ajax請求所回傳的Deferred成為一個Deferred物件。該物件只會在兩個Ajax請求都完成時才會被解析。我們可以註冊一個回呼函數用來處理UI元素在成功時的呈現。附帶一題,我們僅需指定一次錯誤處理在一個地方。
var name = $.post('/echo/json/', {json:JSON.stringify({'name':"Matt Baker"}) });
var lastUpdate = $.post('/echo/json', {json:JSON.stringify({'lastUpdate':"Hello World"})});
$.when(name, lastUpdate)
.done(function(nameResponse, lastUpdateResponse) {
var name = nameResponse[0].name;
var lastUpdate = lastUpdateResponse[0].lastUpdate;
$("#render-me").html(name+"'s last update was: "+lastUpdate);
}).fail(function() {
$("#error").html("an error occured").show();
});
//為什麼陣列會出現在done函數中?
//新的done回呼函數接收所有來自每一個在$.when()中的Deferred物件的done回呼函數所接收到的資料。
//在本例中,我們取得兩個陣列,一個是來自傳送到done方法回呼函數的name請求,另一個是傳送到done方法回呼函數的lastUpdate請求。
轉換型式成pipe()
jQuery中有一個令人感興趣的Deferred物件的API: pipe()。pipe()允許你將Deferred的結果轉型(jQuery稱其為過濾器)。你可以提供一個函數用來修改reject()或resolove()傳入你回呼函數的值,或是該函數可以修改Deferred物件的狀態。
讓我們來看一個範例。考慮以下情境。
你的JSON API是藉由JSON回應的某個旗標來描述錯誤(例: {error:true})而不是藉由傳送Http狀態碼來標示錯誤。你的JSON API總是回傳Http狀態碼200。
在此狀況下,假入在Http狀態為200下的錯誤發生了。由於promise只有在Http請求失敗時才會是拒絕。這樣一來你就必須要自行處理你的錯誤處理流程於success的回呼函數中,而不是fail的回呼函數中。
$.post('/echo/json/',{json: JSON.stringify({'error':true}) })
.done(function(response) {
if(response.error) {
$("#status").html("An error occurred");
}else {
$("#status").html("Success!");
}
}).fail(function(response) {
$("#status").html("A server error occured(failing http status code)");
});
很明顯地,這不是一個理想的解決方案。藉由強大的pipe(),我們就可以創建一個新的Deferred物件,它可以欄截Ajax請求的解析。我們會檢查JSON回應中的error旗標。假若為true,則pipe將回傳一個已拒絕的Deferred物件,它會令fail的回呼函數被執行。
var myXhrDeferred =
$.post('/echo/json', {json:JSON.stringify({'error':true})})
.pipe(function(response) {
if(response.error){
return $.Deferred().reject(response);
}
return response;
},
function() {
return $.Deferred().reject({error:true});
}
);
myXhrDeferred .done(function(response) {
$("#status").html("Success!");
}).fail(function(response) {
$("#status").html("An error occurred");
});
全民非同步
實際上,在你的網站或是應用程式上有著許多的非同步。考慮如下情境:
你的網站要求使用者創建一個會員資料。為了要鼓勵他們把資料填完,你會顯示一個資料完整度進度表。當它們填完後你會想要顯示一個"感謝"的訊息。
在此案例中,Deferred描述的是會員資料的延遲程度。本質上是在創建或計算填寫資料實際上卻是計算人類的行為。在這個狀況下你可能不會去考慮到非同步,但可以藉由Deferred來進行驗證。我們可以使用notify()來通知使用者行為的Deferred物件(也因此Deferred成了Proxy),並且resolve()可以通知已完成。我們可以在progress()上註冊回呼函數來更新進度表,並且使用done()來顯示"感謝"訊息。
var userProgress = $.Deferred();
var $profileFileds = $("input");
var totalFields = $profileFields.length;
userProgress.progress(function(filledFields) {
var pctComplete = (filledFields/totalFields)*100;
$("#progress").html(pctComplete.toFixed(0));
});
userProgress.done(function() {
$("#thanks").html("Thanks for completing your profile!").show();
});
$("input").on("change", function() {
var filledFields = $profileFields.filter("[value!='']").length;
userProgress.notify(filledFields);
if(filledFields == totalFields) {
userProgress.resolve();
}
});
Deferred無所不在
為了處理每個開發人員都得面對的非同步處理,Deferred樣式是簡單,強大,並且廣泛應用的:
原文鏈結:http://eng.wealthfront.com/2012/12/jquerydeferred-is-most-important-client.html