在jQuery 1.5中使用deferred對象的代碼(翻譯)
譯者注:
1. Deferred是jQuery1.5新增的一個(gè)特性,很多人把它翻譯成 “異步隊(duì)列”,我覺得比較靠譜,畢竟和“延遲”沒啥關(guān)系,不過這篇文章中我還采用deferred這個(gè)單詞。
2. 這篇文章在jQuery1.5發(fā)布博客中提到,也是目前介紹deferred比較經(jīng)典和深入的文章。鑒于目前中文資料比較少,特別翻譯出來供大家學(xué)習(xí)參考。
3. 通篇采用意譯的方式,如有不當(dāng)還請大家提出。
jQuery1.5中新增的Deferreds對象,可以將任務(wù)完成的處理方式與任務(wù)本身解耦合。這在JavaScript社區(qū)沒什么新意,因?yàn)镸ochikit和Dojo兩個(gè)JS框架已經(jīng)實(shí)現(xiàn)了這個(gè)特性很長一段時(shí)間了。但是隨著Julian Aubourg對jQuery1.5中AJAX模塊的重寫,deferreds理所當(dāng)然成為了內(nèi)部的實(shí)現(xiàn)邏輯。使用deferreds對象,多個(gè)回調(diào)函數(shù)可以被綁定在任務(wù)完成時(shí)執(zhí)行,甚至可以在任務(wù)完成后綁定這些回調(diào)函數(shù)。這些任務(wù)可以是異步的,也可以是同步的。
更重要的是,deferreds已經(jīng)作為$.ajax()的內(nèi)部實(shí)現(xiàn),所以你可以在調(diào)用AJAX時(shí)自動(dòng)獲取deferreds帶來的遍歷。比如我們可以這樣綁定回調(diào)函數(shù):
// $.get, 異步的AJAX請求
var req = $.get('foo.htm').success(function (response) {
// AJAX成功后的處理函數(shù)
}).error(function () {
// AJAX失敗后處理函數(shù)
});
// 這個(gè)函數(shù)有可能在AJAX結(jié)束前調(diào)用
doSomethingAwesome();
// 添加另外一個(gè)AJAX回調(diào)函數(shù),此時(shí)AJAX或許已經(jīng)結(jié)束,或許還沒有結(jié)束
// 由于$.ajax內(nèi)置了deferred的支持,所以我們可以這樣寫
req.success(function (response) {
// 這個(gè)函數(shù)會(huì)在AJAX結(jié)束后被調(diào)用,或者立即被調(diào)用如果AJAX已經(jīng)結(jié)束
});
我們不再被限制到只有一個(gè)成功,失敗或者完成的回調(diào)函數(shù)了。相反這些隨時(shí)被添加的回調(diào)函數(shù)被放置在一個(gè)先進(jìn)先出的隊(duì)列中。
從上面例子看出,回調(diào)函數(shù)可以被附加到AJAX請求中(任何可觀察的任務(wù)observable task),甚至在AJAX請求已經(jīng)結(jié)束。對于代碼的組織是很好的,我們再也不用寫很長的回調(diào)函數(shù)了。這就像$.queue()遇到了pub/sub(發(fā)布訂閱機(jī)制,一般用在基于事件處理的模型中).
更深入一些,想象這樣一個(gè)場景,在一些并發(fā)的AJAX請求全部結(jié)束之后執(zhí)行一個(gè)回調(diào)函數(shù)。我可以方便的通過jQuery的函數(shù)$.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中打開示例
上面的示例能夠正常運(yùn)行,這要?dú)w功于每個(gè)jQuery的AJAX方法返回值都包含一個(gè)promise函數(shù),用來跟蹤異步請求。Promise函數(shù)的返回值是deferred對象的一個(gè)只讀視圖。(The promise is a read-only view into the result of the task.)Deferreds通過檢測對象中是否存在promise()函數(shù)來判斷當(dāng)前對象是否可觀察。$.when()會(huì)等待所有的AJAX請求結(jié)束,然后調(diào)用通過 .then(), .fail()注冊的回調(diào)函數(shù)(具體調(diào)用哪些回調(diào)函數(shù)取決于任務(wù)的結(jié)束狀態(tài))。這些回調(diào)函數(shù)會(huì)按照他們的注冊順序執(zhí)行。
更好的是,$.when()接受函數(shù)或者函數(shù)的數(shù)組為參數(shù)(譯者注:這點(diǎn)不大對,$.when接受一個(gè)或多個(gè)deferred對象,或者原生的JS對象。注意不能以函數(shù)數(shù)組為參數(shù)),這樣你就可以隨意組合這些異步任務(wù)。
$.ajax()返回一個(gè)對象,這個(gè)對象關(guān)聯(lián)一些deferred函數(shù),比如promise(), then(), success(), error()。然而你不能操作原始的deferred對象,只有promise()函數(shù)(譯者注:還記得剛才提到的promise是只讀視圖),以及可以檢測deferred狀態(tài)的isRejected() 以及isResolved()函數(shù)。
但是為什么不返回deferred對象呢?如果返回了完整的deferred對象,那么我們就擁有更多的控制,或許可以隨意的觸發(fā)(譯者注:我把resolve翻譯成觸發(fā),就是觸發(fā)所有注冊到deferred對象上的回調(diào)函數(shù))deferred對象,從而導(dǎo)致所有回調(diào)函數(shù)在AJAX請求結(jié)束之前執(zhí)行。因此,為了避免不期望的觸發(fā)deferred的風(fēng)險(xiǎn),我們應(yīng)該只返回dfd.promise().(Therefore, to avoid potentially breaking the whole paradigm, only return the dfd.promise().)(譯者注:如果你很迷惑上面幾段話的確切意思,沒關(guān)系,我隨后會(huì)寫一篇文章深層次分析其中原因
注冊回調(diào)函數(shù)(Registering Callbacks)
上面的例子中,我們使用then(), success(), fail()方法來注冊回調(diào)函數(shù),其實(shí)還有更多的方法可以使用,特別在處理AJAX請求時(shí)。具體使用哪種方式取決于你對結(jié)果狀態(tài)的關(guān)注。
所有deferred對象都有的函數(shù) (AJAX, $.when 或者手工創(chuàng)建的deferred對象):
.then( doneCallbacks, failedCallbacks )
.done( doneCallbacks )
.fail( failCallbacks )
AJAX對象包含3個(gè)額外的方法,其中兩個(gè)會(huì)映射到上面提到的方法。這些方法主要是為了兼容以前的代碼:
// "success" 和 "error" 會(huì)分別映射到 "done" and "fail" 兩個(gè)方法
.success( doneCallbacks )
.error( failCallbacks )
你也可以注冊一個(gè)complete的回調(diào)函數(shù),它會(huì)在請求結(jié)束后調(diào)用,而不管這個(gè)請求是成功或者失敗。不像success或者error函數(shù),complete函數(shù)其實(shí)是一個(gè)單獨(dú)的deferred對象的done函數(shù)別名。這個(gè)在$.ajax()內(nèi)部創(chuàng)建的deferred對象,會(huì)在AJAX結(jié)束后觸發(fā)回調(diào)函數(shù)(resolve)。
.complete( completeCallbacks )
因此,下面的3個(gè)例子是等價(jià)的(在AJAX的上下文中,success看起來比done函數(shù)會(huì)舒服點(diǎn),對么?)(譯者注:其實(shí)是因?yàn)槲覀兪煜ひ郧暗腁JAX調(diào)用方式,先入為主罷了,或者叫思維定勢):
$.get("/foo/").done( fn );
// 等價(jià)于:
$.get("/foo/").success( fn );
// 等價(jià)于:
$.get("/foo/", fn );
創(chuàng)建自己的deferred對象(Creating your own Deferred)
我們知道$.ajax和$.when在內(nèi)部實(shí)現(xiàn)了deferred接口,不過我們也可以手工創(chuàng)建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'是服務(wù)器端返回(譯者注:也就是getData中AJAX的結(jié)果)
});
在jsFiddle中打開示例
在showDiv()中,我們創(chuàng)建了一個(gè)deferred對象,執(zhí)行了一段動(dòng)畫,然后返回promise。這個(gè)deferred對象會(huì)在fadeIn()結(jié)束后被觸發(fā)(resolved)。在這個(gè)promise返回和deferred對象(注意:這里的deferred指的是$.when創(chuàng)建的對象,而非showDiv()返回的對象)觸發(fā)的中間,一個(gè)then()回調(diào)函數(shù)會(huì)被注冊。這個(gè)回調(diào)函數(shù)會(huì)在兩個(gè)異步的任務(wù)全部結(jié)束后執(zhí)行。
getData()返回一個(gè)對象(譯者注:其實(shí)是jQuery封裝的XMLHttpRequest對象)擁有promise方法,這就允許$.when()監(jiān)視本次AJAX請求的結(jié)束。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()。因此下面的兩端代碼是等價(jià)的:
function showDiv() {
var dfd = $.Deferred();
$('#foo').fadeIn(1000, dfd.resolve);
return dfd.promise();
}
// 等價(jià)于:
function showDiv() {
return $.Deferred(function (dfd) {
$('#foo').fadeIn(1000, dfd.resolve);
}).promise();
}
為自定義的deferred對象添加回調(diào)函數(shù)(Defer your Deferreds)
我們可以更進(jìn)一步,為getData()和showDiv()分別注冊回調(diào)函數(shù),如同我們在$.then()中注冊回調(diào)函數(shù)一樣。(譯者注:下面的段落內(nèi)容重復(fù),說的都是一個(gè)意思,就不翻譯了,看代碼吧)
function getData() {
return $.get('/foo/').success(function () {
console.log('Fires after the AJAX request succeeds');
});
}
function showDiv() {
return $.Deferred(function (dfd) {
// 譯者注:這段代碼是原文沒有的,但是在jsFiddle中出現(xiàn)。
// 我覺得這是作者的原意,為自定義的deferred函數(shù)注冊回調(diào)函數(shù)
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'是服務(wù)器返回結(jié)果
});
在jsFiddle中打開示例
鏈?zhǔn)酱a(Chaining Hotness)
Deferred的回調(diào)函數(shù)可以鏈?zhǔn)秸{(diào)用,只要函數(shù)返回的是deferred對象(譯者注:dfd.promise()返回的是只讀的deferred對象)。這是一個(gè)實(shí)際的代碼 (via @ajpiano!)
function saveContact(row) {
var form = $.tmpl(templates["contact-form"]),
valid = true,
messages = [],
dfd = $.Deferred();
/*
* 這里方式客戶端驗(yàn)證代碼
*/
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) {
// 客戶端驗(yàn)證通過,并且保存數(shù)據(jù)成功
} else {
// 客戶端驗(yàn)證失敗
// 輸出錯(cuò)誤信息
}
}).fail(function (err) {
// AJAX請求失敗
});
saveContact()函數(shù)首先驗(yàn)證表單數(shù)據(jù)的有效性,然后把有效性狀態(tài)保存在變量valid中。如果驗(yàn)證失敗,直接deferred會(huì)被觸發(fā)(把一個(gè)包含success狀態(tài)碼和錯(cuò)誤信息的JS對象作為參數(shù)傳遞給回調(diào)函數(shù))。如果驗(yàn)證通過,則向服務(wù)器提交數(shù)據(jù),在AJAX成功完成后觸發(fā)deferred對象。fail()會(huì)處理404, 500等可以阻止AJAX請求成功完成的HTTP狀態(tài)碼。
不可觀察的任務(wù)(Non-observable Tasks)
Deferreds對于解耦任務(wù)與任務(wù)處理函數(shù)時(shí)非常有用,而不管是異步任務(wù)或者同步任務(wù)。一個(gè)任務(wù)可能會(huì)返回promise,但也可以返回字符串,對象或者其他類型。
在這個(gè)例子中,當(dāng)“Lanch Application”鏈接被首次點(diǎn)擊時(shí),一個(gè)AJAX請求會(huì)發(fā)送到服務(wù)器并返回當(dāng)前時(shí)間戳。然后這個(gè)時(shí)間戳?xí)槐4娴竭@個(gè)鏈接的data緩存中。當(dāng)這個(gè)鏈接再次被點(diǎn)擊時(shí),只是簡單的從緩存中取出這個(gè)時(shí)間戳返回,而不會(huì)發(fā)出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();
});
當(dāng)$.when()發(fā)現(xiàn)它的第一個(gè)參數(shù)沒有promise函數(shù)(因此不可觀察),它就會(huì)創(chuàng)建一個(gè)新的deferred對象,觸發(fā)deferred對象,并返回promise只讀對象。因此,任意不可觀察的任務(wù)也能傳遞到$.when()中。
需要注意的一個(gè)問題是,如果一個(gè)對象自身擁有promise函數(shù),則這個(gè)對象將不能作為deferred對象。jQuery判斷一個(gè)對象是否deferred,是通過查看它是否有promise函數(shù)來決定的,但是jQuery并不會(huì)檢查這個(gè)promise是否真的返回一個(gè)可用的對象。因此下面的代碼將會(huì)出錯(cuò):
var obj = {
promise: function () {
// do something
}
};
$.when(obj).then(fn);
結(jié)論(Conclusion)
Deferreds提出了一種新的健壯的方式來處理異步任務(wù)。和傳統(tǒng)的將代碼組織到一個(gè)回調(diào)函數(shù)中不同,新的deferred對象允許我們在任何時(shí)候(甚至在任務(wù)結(jié)束后)綁定多個(gè)回調(diào)函數(shù),而這些回調(diào)函數(shù)會(huì)以先進(jìn)先出的方式被調(diào)用。這篇文章中的信息可能比較難以消化,不過一旦你掌握了deferred對象的使用,你會(huì)發(fā)現(xiàn)組織異步執(zhí)行的代碼將會(huì)非常容易。
本文章由三生石上原創(chuàng),博客園首發(fā),轉(zhuǎn)載請注明出處
- jQuery中deferred對象使用方法詳解
- 詳解jQuery中的deferred對象的使用(一)
- jQuery通過deferred對象管理ajax異步
- jQuery的promise與deferred對象在異步回調(diào)中的作用
- jQuery.deferred對象使用詳解
- 以jQuery中$.Deferred對象為例講解promise對象是如何處理異步問題
- jQuery的deferred對象詳解
- jQuery之Deferred對象詳解
- jquery基礎(chǔ)教程之deferred對象使用方法
- 利用jQuery的deferred對象實(shí)現(xiàn)異步按順序加載JS文件
- jQuery的deferred對象使用詳解
- 在jQuery1.5中使用deferred對象 著放大鏡看Promise
- jQuery的deferred對象使用詳解
相關(guān)文章
jQuery中用on綁定事件時(shí)需注意的事項(xiàng)
本篇文章主要介紹了jQuery中用on綁定事件時(shí)需注意的事項(xiàng),具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-03-03jQuery UI實(shí)現(xiàn)動(dòng)畫效果代碼分享
這篇文章給大家總結(jié)了jQuery UI實(shí)現(xiàn)動(dòng)畫效果的實(shí)例代碼,有需要的朋友們可以參考測試下。2018-08-08jquery插件jTimer(jquery定時(shí)器)使用方法
很多時(shí)候我們需要按時(shí)間間隔執(zhí)行一個(gè)任務(wù),當(dāng)滿足一定條件時(shí)停止執(zhí)行.此插件旨在解決這一經(jīng)常遇到的問題2013-12-12JQuery控制div外點(diǎn)擊隱藏而div內(nèi)點(diǎn)擊不會(huì)隱藏的方法
這篇文章主要介紹了JQuery控制div外點(diǎn)擊隱藏而div內(nèi)點(diǎn)擊不會(huì)隱藏的方法,涉及show、hide及stopPropagation等方法的使用技巧,需要的朋友可以參考下2015-01-01分享一個(gè)自己動(dòng)手寫的jQuery分頁插件
本文主要是將自己動(dòng)手些jquery分頁插件的思路和步驟分享給大家,本分頁插件功能很簡單,但是卻很實(shí)用,不想其他插件似的,功能一大堆。好了,廢話不多說,還是看正文吧2014-08-08jQuery新的事件綁定機(jī)制on()示例應(yīng)用
從jQuery1.7開始,jQuery引入了全新的事件綁定機(jī)制,on()和off()兩個(gè)函數(shù)統(tǒng)一處理事件綁定,下面通過示例為大家介紹下2014-07-07