W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
deferred對(duì)象代表了將要完成的某種操作,并提供了一些方法,幫助用戶使用。它是jQuery對(duì)Promises接口的實(shí)現(xiàn)。jQuery的所有Ajax操作函數(shù),默認(rèn)返回的就是一個(gè)deferred對(duì)象。
簡(jiǎn)單說(shuō),Promises是異步操作的通用接口,扮演代理人(proxy)的角色,將異步操作包裝成具有同步操作特性的特殊對(duì)象。異步操作的典型例子就是Ajax操作、網(wǎng)頁(yè)動(dòng)畫(huà)、web worker等等。
由于JavaScript單線程的特點(diǎn),如果某個(gè)操作耗時(shí)很長(zhǎng),其他操作就必需排隊(duì)等待。為了避免整個(gè)程序失去響應(yīng),通常的解決方法是將那些排在后面的操作,寫(xiě)成“回調(diào)函數(shù)”(callback)的形式。這樣做雖然可以解決問(wèn)題,但是有一些顯著缺點(diǎn):
Promises就是為了解決這些問(wèn)題而提出的,它的主要目的就是取代回調(diào)函數(shù),成為非同步操作的解決方案。它的核心思想就是讓非同步操作返回一個(gè)對(duì)象,其他操作都針對(duì)這個(gè)對(duì)象來(lái)完成。比如,假定ajax操作返回一個(gè)Promise對(duì)象。
var promise = get('http://www.example.com');
然后,Promise對(duì)象有一個(gè)then方法,可以用來(lái)指定回調(diào)函數(shù)。一旦非同步操作完成,就調(diào)用指定的回調(diào)函數(shù)。
promise.then(function (content) {
console.log(content)
})
可以將上面兩段代碼合并起來(lái),這樣程序的流程看得更清楚。
get('http://www.example.com').then(function (content) {
console.log(content)
})
在1.7版之前,jQuery的Ajax操作采用回調(diào)函數(shù)。
$.ajax({
url:"/echo/json/",
success: function(response)
{
console.info(response.name);
}
});
1.7版之后,Ajax操作直接返回Promise對(duì)象,這意味著可以用then方法指定回調(diào)函數(shù)。
$.ajax({
url: "/echo/json/",
}).then(function (response) {
console.info(response.name);
});
(1)生成deferred對(duì)象
第一步是通過(guò)$.Deferred()方法,生成一個(gè)deferred對(duì)象。
var deferred = $.Deferred();
(2)deferred對(duì)象的狀態(tài)
deferred對(duì)象有三種狀態(tài)。
state方法用來(lái)返回deferred對(duì)象當(dāng)前狀態(tài)。
$.Deferred().state() // 'pending'
$.Deferred().resolve().state() // 'resolved'
$.Deferred().reject().state() // 'rejected'
(3)改變狀態(tài)的方法
resolve方法將deferred對(duì)象的狀態(tài)從pending改為resolved,reject方法則將狀態(tài)從pending改為rejected。
var deferred = $.Deferred();
deferred.resolve("hello world");
resolve方法的參數(shù),用來(lái)傳遞給回調(diào)函數(shù)。
(4)綁定回調(diào)函數(shù)
deferred對(duì)象在狀態(tài)改變時(shí),會(huì)觸發(fā)回調(diào)函數(shù)。
done方法指定狀態(tài)變?yōu)閞esolved(操作成功)時(shí)的回調(diào)函數(shù);fail方法指定狀態(tài)變?yōu)閞ejected(操作失?。r(shí)的回調(diào)函數(shù);always方法指定,不管狀態(tài)變?yōu)閞esolved或rejected,都會(huì)觸發(fā)的方法。
var deferred = $.Deferred();
deferred.done(function(value) {
console.log(value);
}).resolve('hello world');
// hello world
上述三種方法都返回的原有的deferred對(duì)象,因此可以采用鏈?zhǔn)綄?xiě)法,在后面再鏈接別的方法(包括done和fail在內(nèi))。
$.Deferred().done(f1).fail(f2).always(f3);
progress()用來(lái)指定一個(gè)回調(diào)函數(shù),當(dāng)調(diào)用notify()方法時(shí),該回調(diào)函數(shù)將執(zhí)行。它的用意是提供一個(gè)接口,使得在非同步操作執(zhí)行過(guò)程中,可以執(zhí)行某些操作,比如定期返回進(jìn)度條的進(jìn)度。
var userProgress = $.Deferred();
var $profileFields = $("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();
}
});
(1)概述
then方法的作用也是指定回調(diào)函數(shù),它可以接受三個(gè)參數(shù),也就是三個(gè)回調(diào)函數(shù)。第一個(gè)參數(shù)是resolve時(shí)調(diào)用的回調(diào)函數(shù)(相當(dāng)于done方法),第二個(gè)參數(shù)是reject時(shí)調(diào)用的回調(diào)函數(shù)(相當(dāng)于fail方法),第三個(gè)參數(shù)是progress()方法調(diào)用的回調(diào)函數(shù)。
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
(2)返回值
在jQuery 1.8之前,then()只是.done().fail()寫(xiě)法的語(yǔ)法糖,兩種寫(xiě)法是等價(jià)的。在jQuery 1.8之后,then()返回一個(gè)新的promise對(duì)象,而done()返回的是原有的deferred對(duì)象。如果then()指定的回調(diào)函數(shù)有返回值,該返回值會(huì)作為參數(shù),傳入后面的回調(diào)函數(shù)。
var defer = jQuery.Deferred();
defer.done(function(a,b){
return a * b;
}).done(function( result ) {
console.log("result = " + result);
}).then(function( a, b ) {
return a * b;
}).done(function( result ) {
console.log("result = " + result);
}).then(function( a, b ) {
return a * b;
}).done(function( result ) {
console.log("result = " + result);
});
defer.resolve( 2, 3 );
在jQuery 1.8版本之前,上面代碼的結(jié)果是:
result = 2
result = 2
result = 2
在jQuery 1.8版本之后,返回結(jié)果是
result = 2
result = 6
result = NaN
這一點(diǎn)需要特別引起注意。
$.ajax( url1, { dataType: "json" } )
.then(function( data ) {
return $.ajax( url2, { data: { user: data.userId } } );
}).done(function( data ) {
// 從url2獲取的數(shù)據(jù)
});
上面代碼最后那個(gè)done方法,處理的是從url2獲取的數(shù)據(jù),而不是從url1獲取的數(shù)據(jù)。
(3)對(duì)返回值的修改
利用then()會(huì)修改返回值這個(gè)特性,我們可以在調(diào)用其他回調(diào)函數(shù)之前,對(duì)前一步操作返回的值進(jìn)行處理。
var post = $.post("/echo/json/")
.then(function(p){
return p.firstName;
});
post.done(function(r){ console.log(r); });
上面代碼先使用then()方法,從返回的數(shù)據(jù)中取出所需要的字段(firstName),所以后面的操作就可以只處理這個(gè)字段了。
有時(shí),Ajax操作返回json字符串里面有一個(gè)error屬性,表示發(fā)生錯(cuò)誤。這個(gè)時(shí)候,傳統(tǒng)的方法只能是通過(guò)done()來(lái)判斷是否發(fā)生錯(cuò)誤。通過(guò)then()方法,可以讓deferred對(duì)象調(diào)用fail()方法。
var myDeferred = $.post('/echo/json/', {json:JSON.stringify({'error':true})})
.then(function (response) {
if (response.error) {
return $.Deferred().reject(response);
}
return response;
},function () {
return $.Deferred().reject({error:true});
}
);
myDeferred.done(function (response) {
$("#status").html("Success!");
}).fail(function (response) {
$("#status").html("An error occurred");
});
上面代碼中,不管是通信出錯(cuò),或者服務(wù)器返回一個(gè)錯(cuò)誤,都會(huì)調(diào)用reject方法,返回一個(gè)新的deferred對(duì)象,狀態(tài)為rejected,因此就會(huì)觸發(fā)fail方法指定的回調(diào)函數(shù)。
關(guān)于error的處理,jQuery的deferred對(duì)象與其他實(shí)現(xiàn)Promises規(guī)范的函數(shù)庫(kù)有一個(gè)重大不同。就是說(shuō),如果deferred對(duì)象執(zhí)行過(guò)程中,拋出一個(gè)非Promises對(duì)象的錯(cuò)誤,那么將不會(huì)被后繼的then方法指定的rejected回調(diào)函數(shù)捕獲,而會(huì)一直傳播到應(yīng)用程序?qū)用妗榱舜a行為與Promises規(guī)范保持一致,建議出錯(cuò)時(shí),總是使用reject方法返回錯(cuò)誤。
d = $.Deferred()
d.then(function(){
throw new Error('err')
}).fail(function(){
console.log('fail')
})
d.resolve()
// Error: err
上面代碼中,then的回調(diào)函數(shù)拋出一個(gè)錯(cuò)誤,按照Promises規(guī)范,應(yīng)該被fail方法的回調(diào)函數(shù)捕獲,但是jQuery的部署是上升到應(yīng)用程序的層面。
(4)回調(diào)函數(shù)的返回值
如果回調(diào)函數(shù)返回deferred對(duì)象,則then方法的返回值將是對(duì)應(yīng)這個(gè)返回值的promise對(duì)象。
var d1 = $.Deferred();
var promise = $.when('Hello').then(function(h){
return $.when(h,d1);
})
promise.done(function (s1,s2) {
console.log(s1);
console.log(s2);
})
d1.resolve('World')
// Hello
// World
上面代碼中,done方法的回調(diào)函數(shù),正常情況下只能接受一個(gè)參數(shù)。但是由于then方法的回調(diào)函數(shù),返回一個(gè)when方法生成的deferred對(duì)象,導(dǎo)致它可以接受兩個(gè)參數(shù)。
pipe方法接受一個(gè)函數(shù)作為參數(shù),表示在調(diào)用then方法、done方法、fail方法、always方法指定的回調(diào)函數(shù)之前,先運(yùn)行pipe方法指定的回調(diào)函數(shù)。它通常用來(lái)對(duì)服務(wù)器返回的數(shù)據(jù)做初步處理。
Promise事實(shí)上的標(biāo)準(zhǔn)是社區(qū)提出的Promise A+規(guī)格,jQuery的實(shí)現(xiàn)并不完全符合Promise A+,主要是對(duì)錯(cuò)誤的處理。
var promise2 = promise1.then(function () {
throw new Error("boom!");
});
上面代碼在回調(diào)函數(shù)中拋出一個(gè)錯(cuò)誤,Promise A+規(guī)定此時(shí)Promise實(shí)例的狀態(tài)變?yōu)閞eject,該錯(cuò)誤被下一個(gè)catch方法指定的回調(diào)函數(shù)捕獲。但是,jQuery的Deferred對(duì)象此時(shí)不會(huì)改變狀態(tài),亦不會(huì)觸發(fā)回調(diào)函數(shù),該錯(cuò)誤一般情況下會(huì)被window.onerror捕獲。換句話說(shuō),在Deferred對(duì)象中,總是必須使用reject方法來(lái)改變狀態(tài)。
(1)概念
一般情況下,從外部改變第三方完成的異步操作(比如Ajax)的狀態(tài)是毫無(wú)意義的。為了防止用戶這樣做,可以在deferred對(duì)象的基礎(chǔ)上,返回一個(gè)針對(duì)它的promise對(duì)象。
簡(jiǎn)單說(shuō),promise對(duì)象就是不能改變狀態(tài)的deferred對(duì)象,也就是deferred的只讀版?;蛘吒ㄋ椎乩斫獬?,promise是一個(gè)對(duì)將要完成的任務(wù)的承諾,排除了其他人破壞這個(gè)承諾的可能性,只能等待承諾方給出結(jié)果。
你可以通過(guò)promise對(duì)象,為原始的deferred對(duì)象添加回調(diào)函數(shù),查詢它的狀態(tài),但是無(wú)法改變它的狀態(tài),也就是說(shuō)promise對(duì)象不允許你調(diào)用resolve和reject方法。
(2)生成promise對(duì)象
deferred對(duì)象的promise方法,用來(lái)生成對(duì)應(yīng)的promise對(duì)象。
function getPromise(){
return $.Deferred().promise();
}
try{
getPromise().resolve("a");
} catch(err) {
console.log(err);
}
// TypeError
上面代碼對(duì)promise對(duì)象,調(diào)用resolve方法,結(jié)果報(bào)錯(cuò)。
jQuery的ajax() 方法返回的就是一個(gè)promise對(duì)象。此外,Animation類操作也可以使用promise方法。
$('body').toggle('blinds').promise().then(
function(){
$('body').toggle('blinds')
}
)
deferred對(duì)象還有一系列輔助方法,使它更方便使用。
$.when()接受多個(gè)deferred對(duì)象作為參數(shù),當(dāng)它們?nèi)窟\(yùn)行成功后,才調(diào)用resolved狀態(tài)的回調(diào)函數(shù),但只要其中有一個(gè)失敗,就調(diào)用rejected狀態(tài)的回調(diào)函數(shù)。它相當(dāng)于將多個(gè)非同步操作,合并成一個(gè)。實(shí)質(zhì)上,when方法為多個(gè)deferred對(duì)象,返回一個(gè)單一的promise對(duì)象。
$.when(
$.ajax( "/main.php" ),
$.ajax( "/modules.php" ),
$.ajax( "/lists.php" )
).then(successFunc, failureFunc);
上面代碼表示,要等到三個(gè)ajax操作都結(jié)束以后,才執(zhí)行then方法指定的回調(diào)函數(shù)。
when方法里面要執(zhí)行多少個(gè)操作,回調(diào)函數(shù)就有多少個(gè)參數(shù),對(duì)應(yīng)前面每一個(gè)操作的返回結(jié)果。
$.when(
$.ajax( "/main.php" ),
$.ajax( "/modules.php" ),
$.ajax( "/lists.php" )
).then(function (resp1, resp2, resp3){
console.log(resp1);
console.log(resp2);
console.log(resp3);
});
上面代碼的回調(diào)函數(shù)有三個(gè)參數(shù),resp1、resp2和resp3,依次對(duì)應(yīng)前面三個(gè)ajax操作的返回結(jié)果。
如果when方法的參數(shù)不是deferred或promise對(duì)象,則直接作為回調(diào)函數(shù)的參數(shù)。
d = $.Deferred()
$.when(d, 'World').done(function (s1, s2){
console.log(s1);
console.log(s2);
})
d.resolve('Hello')
// Hello
// World
上面代碼中,when的第二個(gè)參數(shù)是一個(gè)字符串,則直接作為回調(diào)函數(shù)的第二個(gè)參數(shù)。
此外,如果when方法的參數(shù)都不是deferred或promise對(duì)象,那么when方法的回調(diào)函數(shù)將立即運(yùn)行。
我們可以用deferred對(duì)象寫(xiě)一個(gè)wait方法,表示等待多少毫秒后再執(zhí)行。
$.wait = function(time) {
return $.Deferred(function(dfd) {
setTimeout(dfd.resolve, time);
});
}
使用方法如下。
$.wait(5000).then(function() {
console.log("Hello from the future!");
});
在上面的wait方法的基礎(chǔ)上,還可以改寫(xiě)setTimeout方法,讓其返回一個(gè)deferred對(duì)象。
function doSomethingLater(fn, time) {
var dfd = $.Deferred();
setTimeout(function() {
dfd.resolve(fn());
}, time || 0);
return dfd.promise();
}
var promise = doSomethingLater(function (){
console.log( '已經(jīng)延遲執(zhí)行' );
}, 100);
我們可以利用deferred接口,使得任意操作都可以用done()和fail()指定回調(diào)函數(shù)。
Twitter = {
search:function(query) {
var dfd = $.Deferred();
$.ajax({
url:"http://search.twitter.com/search.json",
data:{q:query},
dataType:'jsonp',
success:dfd.resolve
});
return dfd.promise();
}
}
使用方法如下。
Twitter.search('javaScript').then(function(data) {
alert(data.results[0].text);
});
deferred對(duì)象的另一個(gè)優(yōu)勢(shì)是可以附加多個(gè)回調(diào)函數(shù)。下面的例子使用了上面所改寫(xiě)的setTimeout函數(shù)。
function doSomething(arg) {
var dfd = $.Deferred();
setTimeout(function() {
dfd.reject("Sorry, something went wrong.");
});
return dfd;
}
doSomething("uh oh").done(function() {
console.log("Won't happen, we're erroring here!");
}).fail(function(message) {
console.log(message);
});
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: