在jQuery 1.5中使用deferred對象的代碼(翻譯)

來源:互聯網
上載者:User

譯者註:
1. Deferred是jQuery1.5新增的一個特性,很多人把它翻譯成 “非同步隊列”,我覺得比較靠譜,畢竟和“延遲”沒啥關係,不過這篇文章中我還採用deferred這個單詞。

2. 這篇文章在jQuery1.5發布部落格中提到,也是目前介紹deferred比較經典和深入的文章。鑒於目前中文資料比較少,特別翻譯出來供大家學習參考。

3. 通篇採用意譯的方式,如有不當還請大家提出。

jQuery1.5中新增的Deferreds對象,可以將任務完成的處理方式與任務本身解耦合。這在JavaScript社區沒什麼新意,因為Mochikit和Dojo兩個JS架構已經實現了這個特性很長一段時間了。但是隨著Julian Aubourg對jQuery1.5中AJAX模組的重寫,deferreds理所當然成為了內部的實現邏輯。使用deferreds對象,多個回呼函數可以被綁定在任務完成時執行,甚至可以在任務完成後綁定這些回呼函數。這些任務可以是非同步,也可以是同步的。

更重要的是,deferreds已經作為$.ajax()的內部實現,所以你可以在調用AJAX時自動擷取deferreds帶來的遍曆。比如我們可以這樣綁定回呼函數:
複製代碼 代碼如下:
// $.get, 非同步AJAX請求
var req = $.get('foo.htm').success(function (response) {
// AJAX成功後的處理函數
}).error(function () {
// AJAX失敗後處理函數
});
// 這個函數有可能在AJAX結束前調用
doSomethingAwesome();
// 添加另外一個AJAX回呼函數,此時AJAX或許已經結束,或許還沒有結束
// 由於$.ajax內建了deferred的支援,所以我們可以這樣寫
req.success(function (response) {
// 這個函數會在AJAX結束後被調用,或者立即被調用如果AJAX已經結束
});

我們不再被限制到只有一個成功,失敗或者完成的回呼函數了。相反這些隨時被添加的回呼函數被放置在一個先進先出的隊列中。
從上面例子看出,回呼函數可以被附加到AJAX請求中(任何可觀察的任務observable task),甚至在AJAX請求已經結束。對於代碼的組織是很好的,我們再也不用寫很長的回呼函數了。這就像$.queue()遇到了pub/sub(發布訂閱機制,一般用在基於事件處理的模型中).
更深入一些,想象這樣一個情境,在一些並發的AJAX請求全部結束之後執行一個回呼函數。我可以方便的通過jQuery的函數$.when()來完成:
複製代碼 代碼如下:
function doAjax() {
return $.get('foo.htm');
}

function doMoreAjax() {
return $.get('bar.htm');
}

$.when(doAjax(), doMoreAjax()).then(function () {
console.log('I fire once BOTH ajax requests have completed!');
}).fail(function () {
console.log('I fire if one or more requests failed.');
});

在jsFiddle中開啟樣本

上面的樣本能夠正常運行,這要歸功於每個jQuery的AJAX方法傳回值都包含一個promise函數,用來跟蹤非同步請求。Promise函數的傳回值是deferred對象的一個唯讀視圖。(The promise is a read-only view into the result of the task.)Deferreds通過檢測對象中是否存在promise()函數來判斷當前對象是否可觀察。$.when()會等待所有的AJAX請求結束,然後調用通過 .then(), .fail()註冊的回呼函數(具體調用哪些回呼函數取決於任務的結束狀態)。這些回呼函數會按照他們的註冊順序執行。

更好的是,$.when()接受函數或者函數的數組為參數(譯者註:這點不大對,$.when接受一個或多個deferred對象,或者原生的JS對象。注意不能以函數數組為參數),這樣你就可以隨意組合這些非同步任務。

$.ajax()返回一個對象,這個對象關聯一些deferred函數,比如promise(), then(), success(), error()。然而你不能操作原始的deferred對象,只有promise()函數(譯者註:還記得剛才提到的promise是唯讀視圖),以及可以檢測deferred狀態的isRejected() 以及isResolved()函數。

但是為什麼不返回deferred對象呢?如果返回了完整的deferred對象,那麼我們就擁有更多的控制,或許可以隨意的觸發(譯者註:我把resolve翻譯成觸發,就是觸發所有註冊到deferred對象上的回呼函數)deferred對象,從而導致所有回呼函數在AJAX請求結束之前執行。因此,為了避免不期望的觸發deferred的風險,我們應該只返回dfd.promise().(Therefore, to avoid potentially breaking the whole paradigm, only return the dfd.promise().)(譯者註:如果你很迷惑上面幾段話的確切意思,沒關係,我隨後會寫一篇文章深層次分析其中原因
註冊回呼函數(Registering Callbacks)
上面的例子中,我們使用then(), success(), fail()方法來註冊回呼函數,其實還有更多的方法可以使用,特別在處理AJAX請求時。具體使用哪種方式取決於你對結果狀態的關注。
所有deferred對象都有的函數 (AJAX, $.when 或者手工建立的deferred對象):
複製代碼 代碼如下:
.then( doneCallbacks, failedCallbacks )
.done( doneCallbacks )
.fail( failCallbacks )

AJAX對象包含3個額外的方法,其中兩個會映射到上面提到的方法。這些方法主要是為了相容以前的代碼:
複製代碼 代碼如下:
// "success" 和 "error" 會分別映射到 "done" and "fail" 兩個方法
.success( doneCallbacks )
.error( failCallbacks )

你也可以註冊一個complete的回呼函數,它會在請求結束後調用,而不管這個請求是成功或者失敗。不像success或者error函數,complete函數其實是一個單獨的deferred對象的done函數別名。這個在$.ajax()內部建立的deferred對象,會在AJAX結束後觸發回呼函數(resolve)。
複製代碼 代碼如下:
.complete( completeCallbacks )

因此,下面的3個例子是等價的(在AJAX的上下文中,success看起來比done函數會舒服點,對嗎?)(譯者註:其實是因為我們熟悉以前的AJAX調用方式,先入為主罷了,或者叫思維定勢):
複製代碼 代碼如下:
$.get("/foo/").done( fn );
// 等價於:
$.get("/foo/").success( fn );
// 等價於:
$.get("/foo/", fn );

建立自己的deferred對象(Creating your own Deferred)
我們知道$.ajax和$.when在內部實現了deferred介面,不過我們也可以手工建立deferred對象:
複製代碼 代碼如下:
function getData() {
return $.get('/foo/');
}
function showDiv() {
var dfd = $.Deferred();
$('#foo').fadeIn(1000, dfd.resolve);
return dfd.promise();
}
$.when(getData(), showDiv()).then(function (ajaxResult) {
console.log('The animation AND the AJAX request are both done!');
// 'ajaxResult'是伺服器端返回(譯者註:也就是getData中AJAX的結果)
});

在jsFiddle中開啟樣本
在showDiv()中,我們建立了一個deferred對象,執行了一段動畫,然後返回promise。這個deferred對象會在fadeIn()結束後被觸發(resolved)。在這個promise返回和deferred對象(注意:這裡的deferred指的是$.when建立的對象,而非showDiv()返回的對象)觸發的中間,一個then()回呼函數會被註冊。這個回呼函數會在兩個非同步任務全部結束後執行。
getData()返回一個對象(譯者註:其實是jQuery封裝的XMLHttpRequest對象)擁有promise方法,這就允許$.when()監視本次AJAX請求的結束。The manually steps we took to return a promise in showDiv() is handled for us internally by $.ajax() and $.when().
1/15/2011: Julian在評論中指出,上面的文法可以被簡化為$.Deferred(fn).promise()。因此下面的兩端代碼是等價的:
複製代碼 代碼如下:
function showDiv() {
var dfd = $.Deferred();
$('#foo').fadeIn(1000, dfd.resolve);
return dfd.promise();
}
// 等價於:
function showDiv() {
return $.Deferred(function (dfd) {
$('#foo').fadeIn(1000, dfd.resolve);
}).promise();
}

為自訂的deferred對象添加回呼函數(Defer your Deferreds)
我們可以更進一步,為getData()和showDiv()分別註冊回呼函數,如同我們在$.then()中註冊回呼函數一樣。(譯者註:下面的段落內容重複,說的都是一個意思,就不翻譯了,看代碼吧)
複製代碼 代碼如下:
function getData() {
return $.get('/foo/').success(function () {
console.log('Fires after the AJAX request succeeds');
});
}
function showDiv() {
return $.Deferred(function (dfd) {
// 譯者註:這段代碼是原文沒有的,但是在jsFiddle中出現。
// 我覺得這是作者的原意,為自訂的deferred函數註冊回呼函數
dfd.done(function () {
console.log('Fires after the animation succeeds');
});
$('#foo').fadeIn(1000, dfd.resolve);
}).promise();
}
$.when(getData(), showDiv()).then(function (ajaxResult) {
console.log('Fires after BOTH showDiv() AND the AJAX request succeed!');
// 'ajaxResult'是伺服器返回結果
});

在jsFiddle中開啟樣本
鏈式代碼(Chaining Hotness)
Deferred的回呼函數可以鏈式調用,只要函數返回的是deferred對象(譯者註:dfd.promise()返回的是唯讀deferred對象)。這是一個實際的代碼 (via @ajpiano!)
複製代碼 代碼如下:
function saveContact(row) {
var form = $.tmpl(templates["contact-form"]),
valid = true,
messages = [],
dfd = $.Deferred();
/*
* 這裡方式用戶端驗證代碼
*/
if (!valid) {
dfd.resolve({
success: false,
errors: messages
});
} else {
form.ajaxSubmit({
dataType: "json",
success: dfd.resolve,
error: dfd.reject
});
}
return dfd.promise();
};
saveContact(row).then(function (response) {
if (response.success) {
// 用戶端驗證通過,並且儲存資料成功
} else {
// 用戶端驗證失敗
// 輸出錯誤資訊
}
}).fail(function (err) {
// AJAX請求失敗
});

saveContact()函數首先驗證表單資料的有效性,然後把有效性狀態儲存在變數valid中。如果驗證失敗,直接deferred會被觸發(把一個包含success狀態代碼和錯誤資訊的JS對象作為參數傳遞給回呼函數)。如果驗證通過,則向伺服器提交資料,在AJAX成功完成後觸發deferred對象。fail()會處理404, 500等可以阻止AJAX請求成功完成的HTTP狀態代碼。
不可觀察的任務(Non-observable Tasks)
Deferreds對於解耦任務與任務處理函數時非常有用,而不管是非同步任務或者同步任務。一個任務可能會返回promise,但也可以返回字串,對象或者其他類型。
在這個例子中,當“Lanch Application”連結被首次點擊時,一個AJAX請求會發送到伺服器並返回目前時間戳。然後這個時間戳記會被儲存到這個連結的data緩衝中。當這個連結再次被點擊時,只是簡單的從緩衝中取出這個時間戳記返回,而不會發出AJAX請求。
複製代碼 代碼如下:
function startTask(element) {
var timestamp = $.data(element, 'timestamp');
if (timestamp) {
return timestamp;
} else {
return $.get('/start-task/').success(function (timestamp) {
$.data(element, 'timestamp', timestamp);
});
}
}
$('#launchApplication').bind('click', function (event) {
event.preventDefault();
$.when(startTask(this)).done(function (timestamp) {
$('#status').html('<p>You first started this task on: ' + timestamp + '</p>');
});
loadApplication();
});

當$.when()發現它的第一個參數沒有promise函數(因此不可觀察),它就會建立一個新的deferred對象,觸發deferred對象,並返回promise唯讀對象。因此,任意不可觀察的任務也能傳遞到$.when()中。
需要注意的一個問題是,如果一個對象自身擁有promise函數,則這個對象將不能作為deferred對象。jQuery判斷一個對象是否deferred,是通過查看它是否有promise函數來決定的,但是jQuery並不會檢查這個promise是否真的返回一個可用的對象。因此下面的代碼將會出錯:
複製代碼 代碼如下:
var obj = {
promise: function () {
// do something
}
};
$.when(obj).then(fn);

結論(Conclusion)
Deferreds提出了一種新的健壯的方式來處理非同步任務。和傳統的將程式碼群組織到一個回呼函數中不同,新的deferred對象允許我們在任何時候(甚至在任務結束後)綁定多個回呼函數,而這些回呼函數會以先進先出的方式被調用。這篇文章中的資訊可能比較難以消化,不過一旦你掌握了deferred對象的使用,你會發現組織非同步執行的代碼將會非常容易。
本文章由三生石上原創,部落格園首發,轉載請註明出處

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.