Promise 的含義
一句話概括一下promise的作用:可以將異步操作以同步操作的流程表達(dá)出來递雀,避免了層層嵌套的回調(diào)函數(shù)物独。
所謂Promise檩电,簡單說就是一個容器留荔,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果。
Promise 是異步編程的一種解決方案讨阻,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大芥永。可以解決回調(diào)地獄問題钝吮。
什么是回調(diào)地獄
JavaScript要異步埋涧,回調(diào)少不了,但當(dāng)業(yè)務(wù)邏輯復(fù)雜的時候奇瘦,回調(diào)的嵌套過多棘催,代碼復(fù)雜度增加,可讀性降低耳标,維護(hù)起來也復(fù)雜醇坝,調(diào)試也復(fù)雜,這就是回調(diào)地獄次坡。
Promise對象有以下兩個特點(diǎn)
(1)對象的狀態(tài)不受外界影響呼猪。Promise對象代表一個異步操作画畅,有三種狀態(tài):Pending(進(jìn)行中)、Fulfilled(已成功)和Rejected(已失斔尉唷)轴踱。只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài)乡革,任何其他操作都無法改變這個狀態(tài)
(2)一旦狀態(tài)改變寇僧,就不會再變,任何時候都可以得到這個結(jié)果沸版。Promise對象的狀態(tài)改變嘁傀,只有兩種可能:從Pending變?yōu)镕ulfiled(Resolved)和從Pending變?yōu)镽ejected。只要這兩種情況發(fā)生视粮,狀態(tài)就凝固了细办,不會再變了,會一直保持這個結(jié)果蕾殴,這時就稱為 Resolved(已定型)笑撞。如果改變已經(jīng)發(fā)生了,你再對Promise對象添加回調(diào)函數(shù)钓觉,也會立即得到這個結(jié)果茴肥。這與事件(Event)完全不同,事件的特點(diǎn)是荡灾,如果你錯過了它瓤狐,再去監(jiān)聽,是得不到結(jié)果的批幌。
Promise的缺點(diǎn)
Promise也有一些缺點(diǎn)础锐。首先,無法取消Promise荧缘,一旦新建它就會立即執(zhí)行皆警,無法中途取消。其次截粗,如果不設(shè)置回調(diào)函數(shù)信姓,Promise內(nèi)部拋出的錯誤,不會反應(yīng)到外部绸罗。第三意推,當(dāng)處于Pending狀態(tài)時,無法得知目前進(jìn)展到哪一個階段(剛剛開始還是即將完成)从诲。
promise的基本用法
Promise對象是一個構(gòu)造函數(shù),用來生成Promise實(shí)例靡羡。
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise構(gòu)造函數(shù)接受一個函數(shù)作為參數(shù)系洛,該函數(shù)的兩個參數(shù)分別是resolve和reject俊性。它們是兩個函數(shù),
resolve函數(shù)的作用是描扯,將Promise對象的狀態(tài)從“未完成”變?yōu)椤俺晒Α保磸?Pending 變?yōu)?Resolved)定页,在異步操作成功時調(diào)用,并將異步操作的結(jié)果绽诚,作為參數(shù)傳遞出去典徊;reject函數(shù)的作用是,將Promise對象的狀態(tài)從“未完成”變?yōu)椤笆 保磸?Pending 變?yōu)?Rejected)恩够,在異步操作失敗時調(diào)用卒落,并將異步操作報(bào)出的錯誤,作為參數(shù)傳遞出去蜂桶。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then方法可以接受兩個回調(diào)函數(shù)作為參數(shù)儡毕。第一個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)镽esolved時調(diào)用,第二個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)镽ejected時調(diào)用扑媚。其中腰湾,第二個函數(shù)是可選的,不一定要提供疆股。這兩個函數(shù)都接受Promise對象傳出的值作為參數(shù)费坊。
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('Resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// Resolved
Promise 新建后立即執(zhí)行,所以首先輸出的是Promise旬痹。然后附井,then方法指定的回調(diào)函數(shù),將在當(dāng)前腳本所有同步任務(wù)執(zhí)行完才會執(zhí)行唱凯,所以Resolved最后輸出羡忘。
面是一個用Promise對象實(shí)現(xiàn)的 Ajax 操作的例子。
var getJSON = function(url) {
var promise = new Promise(function(resolve, reject){
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出錯了', error);
});
getJSON是對 XMLHttpRequest 對象的封裝磕昼,用于發(fā)出一個針對 JSON 數(shù)據(jù)的 HTTP 請求卷雕,并且返回一個Promise對象
如果調(diào)用resolve函數(shù)和reject函數(shù)時帶有參數(shù),那么它們的參數(shù)會被傳遞給回調(diào)函數(shù)票从。reject函數(shù)的參數(shù)通常是Error對象的實(shí)例漫雕,表示拋出的錯誤,resolve參數(shù)也可以是另一個 Promise 實(shí)例峰鄙。
當(dāng)resolve參數(shù)是另一個 Promise 實(shí)例浸间。
var p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
var p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
上面代碼中,p1是一個Promise吟榴,3秒之后變?yōu)閞ejected魁蒜。p2的狀態(tài)在1秒之后改變,resolve方法返回的是p1。由于p2返回的是另一個 Promise兜看,導(dǎo)致p2自己的狀態(tài)無效了锥咸,由p1的狀態(tài)決定p2的狀態(tài)。所以细移,后面的then語句都變成針對后者(p1)搏予。又過了2秒,p1變?yōu)閞ejected弧轧,導(dǎo)致觸發(fā)catch方法指定的回調(diào)函數(shù)雪侥。
注意,調(diào)用resolve或reject并不會終結(jié) Promise 的參數(shù)函數(shù)的執(zhí)行精绎。
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
上面代碼中速缨,調(diào)用resolve(1)以后,后面的console.log(2)還是會執(zhí)行捺典,并且會首先打印出來鸟廓。這是因?yàn)榱⒓?resolved 的 Promise 是在本輪事件循環(huán)的末尾執(zhí)行,總是晚于本輪循環(huán)的同步任務(wù)襟己。
一般來說引谜,調(diào)用resolve或reject以后,Promise 的使命就完成了擎浴,后繼操作應(yīng)該放到then方法里面员咽,而不應(yīng)該直接寫在resolve或reject的后面。所以贮预,最好在它們前面加上return語句贝室,這樣就不會有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 后面的語句不會執(zhí)行
console.log(2);
})
Promise.prototype.then()
then方法的第一個參數(shù)是Resolved狀態(tài)的回調(diào)函數(shù)仿吞,第二個參數(shù)(可選)是Rejected狀態(tài)的回調(diào)函數(shù)滑频。
then方法返回的是一個新的Promise實(shí)例(注意,不是原來那個Promise實(shí)例)唤冈。因此可以采用鏈?zhǔn)綄懛ㄏ棵裕磘hen方法后面再調(diào)用另一個then方法。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
上面的代碼使用then方法你虹,依次指定了兩個回調(diào)函數(shù)绘搞。第一個回調(diào)函數(shù)完成以后,會將返回結(jié)果作為參數(shù)傅物,傳入第二個回調(diào)函數(shù)夯辖。
采用鏈?zhǔn)降膖hen,可以指定一組按照次序調(diào)用的回調(diào)函數(shù)董饰。這時蒿褂,前一個回調(diào)函數(shù)圆米,有可能返回的還是一個Promise對象(即有異步操作),這時后一個回調(diào)函數(shù)啄栓,就會等待該P(yáng)romise對象的狀態(tài)發(fā)生變化榨咐,才會被調(diào)用。
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("Resolved: ", comments);
}, function funcB(err){
console.log("Rejected: ", err);
});
上面代碼中谴供,第一個then方法指定的回調(diào)函數(shù),返回的是另一個Promise對象齿坷。這時桂肌,第二個then方法指定的回調(diào)函數(shù),就會等待這個新的Promise對象狀態(tài)發(fā)生變化永淌。如果變?yōu)镽esolved崎场,就調(diào)用funcA,如果狀態(tài)變?yōu)镽ejected遂蛀,就調(diào)用funcB谭跨。
Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)的別名,用于指定發(fā)生錯誤時的回調(diào)函數(shù)李滴。
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 處理 getJSON 和 前一個回調(diào)函數(shù)運(yùn)行時發(fā)生的錯誤
console.log('發(fā)生錯誤螃宙!', error);
});
getJSON方法返回一個 Promise 對象,如果該對象狀態(tài)變?yōu)镽esolved所坯,則會調(diào)用then方法指定的回調(diào)函數(shù)谆扎;如果異步操作拋出錯誤,狀態(tài)就會變?yōu)镽ejected芹助,就會調(diào)用catch方法指定的回調(diào)函數(shù)堂湖,處理這個錯誤。另外状土,then方法指定的回調(diào)函數(shù)无蜂,如果運(yùn)行中拋出錯誤,也會被catch方法捕獲蒙谓。
var promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
// Error: test
var promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 寫法二
var promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
reject方法的作用斥季,等同于拋出錯誤。
注意:如果Promise狀態(tài)已經(jīng)變成Resolved彼乌,再拋出錯誤是無效的泻肯。
var promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
Promise 在resolve語句后面,再拋出錯誤慰照,不會被捕獲灶挟,等于沒有拋出。
因?yàn)?Promise 的狀態(tài)一旦改變毒租,就永久保持該狀態(tài)稚铣,不會再變了箱叁。
Promise 對象的錯誤具有“冒泡”性質(zhì),會一直向后傳遞惕医,直到被捕獲為止耕漱。也就是說,錯誤總是會被下一個catch語句捕獲抬伺。
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 處理前面三個Promise產(chǎn)生的錯誤
});
上面代碼中螟够,一共有三個Promise對象:一個由getJSON產(chǎn)生,兩個由then產(chǎn)生峡钓。它們之中任何一個拋出的錯誤妓笙,都會被最后一個catch捕獲。
一般來說能岩,不要在then方法里面定義Reject狀態(tài)的回調(diào)函數(shù)(即then的第二個參數(shù))寞宫,總是使用catch方法,因?yàn)閏atch可以捕獲前面then方法執(zhí)行中的錯誤。
var promise = new Promise(function (resolve, reject) {
resolve('ok');
setTimeout(function () { throw new Error('test') }, 0)
});
promise.then(function (value) { console.log(value) });
// ok
// Uncaught Error: test
上面代碼中拉鹃,Promise 指定在下一輪“事件循環(huán)”再拋出錯誤辈赋。到了那個時候,Promise 的運(yùn)行已經(jīng)結(jié)束了膏燕,所以這個錯誤是在 Promise 函數(shù)體外拋出的钥屈,會冒泡到最外層,成了未捕獲的錯誤坝辫。
Promise.all()
Promise.all方法用于將多個 Promise 實(shí)例焕蹄,包裝成一個新的 Promise 實(shí)例。
var p = Promise.all([p1, p2, p3]);
p的狀態(tài)由p1阀溶、p2腻脏、p3決定,分成兩種情況银锻。
(1)只有p1永品、p2、p3的狀態(tài)都變成fulfilled击纬,p的狀態(tài)才會變成fulfilled鼎姐,此時p1、p2更振、p3的返回值組成一個數(shù)組炕桨,傳遞給p的回調(diào)函數(shù)。
(2)只要p1肯腕、p2献宫、p3之中有一個被rejected,p的狀態(tài)就變成rejected实撒,此時第一個被reject的實(shí)例的返回值姊途,會傳遞給p的回調(diào)函數(shù)涉瘾。
// 生成一個Promise對象的數(shù)組
var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
上面代碼中,promises是包含6個 Promise 實(shí)例的數(shù)組捷兰,只有這6個實(shí)例的狀態(tài)都變成fulfilled立叛,或者其中有一個變?yōu)閞ejected,才會調(diào)用Promise.all方法后面的回調(diào)函數(shù)贡茅。只有等到6個 Promise 實(shí)例的結(jié)果都返回了秘蛇,才會觸發(fā)Promise.all(promises)的回調(diào)函數(shù)
注意,如果作為參數(shù)的 Promise 實(shí)例顶考,自己定義了catch方法彤叉,那么它一旦被rejected,并不會觸發(fā)Promise.all()的catch方法村怪。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('報(bào)錯了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 報(bào)錯了]
上面代碼中,p1會resolved浮庐,p2首先會rejected甚负,但是p2有自己的catch方法,該方法返回的是一個新的 Promise 實(shí)例审残,p2指向的實(shí)際上是這個實(shí)例梭域。該實(shí)例執(zhí)行完catch方法后,也會變成resolved搅轿,導(dǎo)致Promise.all()方法參數(shù)里面的兩個實(shí)例都會resolved病涨,因此會調(diào)用then方法指定的回調(diào)函數(shù),而不會調(diào)用catch方法指定的回調(diào)函數(shù)璧坟。
如果p2沒有自己的catch方法既穆,就會調(diào)用Promise.all()的catch方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('報(bào)錯了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 報(bào)錯了
Promise.race()
var p = Promise.race([p1, p2, p3]);
上面代碼中雀鹃,只要p1幻工、p2、p3之中有一個實(shí)例率先改變狀態(tài)黎茎,p的狀態(tài)就跟著改變囊颅。那個率先改變的 Promise 實(shí)例的返回值(執(zhí)行最快的那個),就傳遞給p的回調(diào)函數(shù)傅瞻。
Promise.resolve()
有時需要將現(xiàn)有對象轉(zhuǎn)為Promise對象踢代,Promise.resolve方法就起到這個作用
Promise.resolve方法的參數(shù)分成四種情況。
(1)參數(shù)是一個Promise實(shí)例
Promise.resolve將不做任何修改嗅骄、原封不動地返回這個實(shí)例胳挎。
(2) 參數(shù)是一個thenable對象
thenable對象指的是具有then方法的對象
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
上面代碼中,thenable對象的then方法執(zhí)行后溺森,對象p1的狀態(tài)就變?yōu)閞esolved串远,從而立即執(zhí)行最后那個then方法指定的回調(diào)函數(shù)宏多,輸出42。
(3)參數(shù)不是具有then方法的對象澡罚,或根本就不是對象
var p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
如果參數(shù)是一個原始值伸但,或者是一個不具有then方法的對象,則Promise.resolve方法返回一個新的Promise對象留搔,狀態(tài)為Resolved更胖。
(4)不帶有任何參數(shù)
Promise.resolve方法允許調(diào)用時不帶參數(shù),直接返回一個Resolved狀態(tài)的Promise對象隔显。
var p = Promise.resolve();
p.then(function () {
// ...
});
需要注意的是却妨,立即resolve的Promise對象,是在本輪“事件循環(huán)”(event loop)的結(jié)束時括眠,而不是在下一輪“事件循環(huán)”的開始時
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
上面代碼中彪标,setTimeout(fn, 0)在下一輪“事件循環(huán)”開始時執(zhí)行,Promise.resolve()在本輪“事件循環(huán)”結(jié)束時執(zhí)行掷豺,console.log('one')則是立即執(zhí)行捞烟,因此最先輸出。
Promise.reject()
生成一個Promise對象的實(shí)例p当船,狀態(tài)為rejected题画,回調(diào)函數(shù)會立即執(zhí)行。
注意德频,Promise.reject()方法的參數(shù)苍息,會原封不動地作為reject的理由,變成后續(xù)方法的參數(shù)壹置。這一點(diǎn)與Promise.resolve方法不一致竞思。
deferred對象
deferred對象就是jQuery的回調(diào)函數(shù)解決方案
deferred的主要作用:不管是ajax操作還是本地操作,也不管是異步操作還是同步操作----都可以使用deferred對象的各種方法钞护,指定回調(diào)函數(shù)衙四。
jquery ajax的傳統(tǒng)寫法
$.ajax({
url: "test.html",
success: function(){
alert("哈哈,成功了患亿!");
},
error:function(){
alert("出錯啦传蹈!");
}
});
$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery步藕,返回的是XHR對象惦界,你沒法進(jìn)行鏈?zhǔn)讲僮鳎蝗绻哂?.5.0版本咙冗,返回的是deferred對象沾歪,可以進(jìn)行鏈?zhǔn)讲僮鳌?br> 新的寫法
$.ajax("test.html")
.done(function(){ alert("哈哈,成功了雾消!"); })
.fail(function(){ alert("出錯啦灾搏!"); });
done()相當(dāng)于success方法挫望,fail()相當(dāng)于error方法。采用鏈?zhǔn)綄懛ㄒ院罂褚ぃa的可讀性大大提高媳板。
deferred對象的幾大優(yōu)點(diǎn)如下
指定同一操作的多個回調(diào)函數(shù)
deferred對象的一大好處,就是它允許你自由添加多個回調(diào)函數(shù)泉哈。
$.ajax("test.html")
.done(function(){ alert("哈哈蛉幸,成功了!");} )
.fail(function(){ alert("出錯啦丛晦!"); } )
.done(function(){ alert("第二個回調(diào)函數(shù)奕纫!");} );
為多個操作指定回調(diào)函數(shù)
deferred對象的另一大好處,就是它允許你為多個事件指定一個回調(diào)函數(shù)烫沙,這是傳統(tǒng)寫法做不到的匹层。
$.when($.ajax("test1.html"), $.ajax("test2.html"))
.done(function(){ alert("哈哈,成功了锌蓄!"); })
.fail(function(){ alert("出錯啦升筏!"); });
先執(zhí)行兩個操作$.ajax("test1.html")和$.ajax("test2.html"),如果都成功了煤率,就運(yùn)行done()指定的回調(diào)函數(shù);如果有一個失敗或都失敗了乏冀,就執(zhí)行fail()指定的回調(diào)函數(shù)蝶糯。
普通操作的回調(diào)函數(shù)接口
deferred對象的最大優(yōu)點(diǎn),就是它把這一套回調(diào)函數(shù)接口辆沦,從ajax操作擴(kuò)展到了所有操作昼捍。也就是說,任何一個操作----不管是ajax操作還是本地操作肢扯,也不管是異步操作還是同步操作----都可以使用deferred對象的各種方法妒茬,指定回調(diào)函數(shù)。
var wait = function(){
var tasks = function(){
alert("執(zhí)行完畢蔚晨!");
};
setTimeout(tasks,5000);
};
給上面的代碼添加回調(diào)
$.when(wait())
.done(function(){ alert("哈哈乍钻,成功了!"); })
.fail(function(){ alert("出錯啦铭腕!"); });
但是银择,這樣寫的話,done()方法會立即執(zhí)行累舷,起不到回調(diào)函數(shù)的作用浩考。
原因在于$.when()的參數(shù)只能是deferred對象,所以必須對wait()進(jìn)行改寫:
var dtd=$.Deferred(); // 新建一個deferred對象
var wait = function(dtd){
var tasks = function(){
alert("執(zhí)行完畢被盈!");
dtd.resolve();// 改變deferred對象的執(zhí)行狀態(tài)
};
setTimeout(tasks,5000);
return dtd;
};
//wait()函數(shù)返回的是deferred對象析孽,這就可以加上鏈?zhǔn)讲僮髁恕? $.when(wait(dtd))
.done(function(){ alert("哈哈搭伤,成功了!"); })
.fail(function(){ alert("出錯啦袜瞬!"); });
wait()函數(shù)運(yùn)行完怜俐,就會自動運(yùn)行done()方法指定的回調(diào)函數(shù)。
前面部分的ajax操作時吞滞,deferred對象會根據(jù)返回結(jié)果佑菩,自動改變自身的執(zhí)行狀態(tài);但是裁赠,在wait()函數(shù)中殿漠,這個執(zhí)行狀態(tài)必須由程序員手動指定。dtd.resolve()的意思是佩捞,將dtd對象的執(zhí)行狀態(tài)從"未完成"改為"已完成"绞幌,從而觸發(fā)done()方法。
deferred.promise()
上面這種寫法一忱,還是有問題莲蜘。那就是dtd是一個全局對象,所以它的執(zhí)行狀態(tài)可以從外部改變帘营。
請看下面的代碼:
var dtd = $.Deferred(); // 新建一個Deferred對象
var wait = function(dtd){
var tasks = function(){
alert("執(zhí)行完畢票渠!");
dtd.resolve(); // 改變Deferred對象的執(zhí)行狀態(tài)
};
setTimeout(tasks,5000);
return dtd;
};
$.when(wait(dtd))
.done(function(){ alert("哈哈,成功了芬迄!"); })
.fail(function(){ alert("出錯啦问顷!"); });
dtd.resolve();
我在代碼的尾部加了一行dtd.resolve(),這就改變了dtd對象的執(zhí)行狀態(tài)禀梳,因此導(dǎo)致done()方法立刻執(zhí)行杜窄,跳出"哈哈,成功了算途!"的提示框塞耕,等5秒之后再跳出"執(zhí)行完畢!"的提示框嘴瓤。
為了避免這種情況扫外,jQuery提供了deferred.promise()用是,在原來的deferred對象上返回另一個deferred對象廓脆,后者只開放與改變執(zhí)行狀態(tài)無關(guān)的方法(比如done()方法和fail()方法)畏浆,屏蔽與改變執(zhí)行狀態(tài)有關(guān)的方法(比如resolve()方法和reject()方法),從而使得執(zhí)行狀態(tài)不能被改變狞贱。
請看下面的代碼:
var dtd = $.Deferred(); // 新建一個Deferred對象
var wait = function(dtd){
var tasks = function(){
alert("執(zhí)行完畢刻获!");
dtd.resolve(); // 改變Deferred對象的執(zhí)行狀態(tài)
}; setTimeout(tasks,5000);
**return dtd.promise(); // 返回promise對象**
};
**var d = wait(dtd); // 新建一個d對象,改為對這個對象進(jìn)行操作**
$.when(d)
.done(function(){ alert("哈哈,成功了蝎毡!"); })
.fail(function(){ alert("出錯啦厚柳!"); });
**d.resolve(); // 此時,這個語句是無效的**
在上面的這段代碼中沐兵,wait()函數(shù)返回的是promise對象别垮。然后,我們把回調(diào)函數(shù)綁定在這個對象上面扎谎,而不是原來的deferred對象上面碳想。這樣的好處是,無法改變這個對象的執(zhí)行狀態(tài)毁靶,要想改變執(zhí)行狀態(tài)胧奔,只能操作原來的deferred對象。
更好的寫法,將dtd對象變成wait()函數(shù)的內(nèi)部對象预吆。
另一種防止執(zhí)行狀態(tài)被外部改變的方法龙填,是使用deferred對象的建構(gòu)函數(shù)$.Deferred()。
var wait = function(dtd){
var tasks = function(){
alert("執(zhí)行完畢拐叉!");
dtd.resolve(); // 改變deferred對象的執(zhí)行狀態(tài)
};
setTimeout(tasks,5000);
return dtd;
};
$.Deferred(wait)
.done(function(){ alert("哈哈岩遗,成功了!"); })
.fail(function(){ alert("出錯啦凤瘦!"); });
jQuery規(guī)定宿礁,$.Deferred()可以接受一個函數(shù)名(注意,是函數(shù)名)作為參數(shù)蔬芥,$.Deferred()所生成的deferred對象將作為這個函數(shù)的默認(rèn)參數(shù)梆靖。
小結(jié):deferred對象的方法
前面已經(jīng)講到了deferred對象的多種方法,下面做一個總結(jié):
“泳ァ(1) $.Deferred() 生成一個deferred對象涤姊。
∠痉(2) deferred.done() 指定操作成功時的回調(diào)函數(shù)
∴头拧(3) deferred.fail() 指定操作失敗時的回調(diào)函數(shù)
(4) deferred.promise() 沒有參數(shù)時壁酬,返回一個新的deferred對象次酌,該對象的運(yùn)行狀態(tài)無法被改變;接受參數(shù)時舆乔,作用為在參數(shù)對象上部署deferred接口岳服。
(5) deferred.resolve() 手動改變deferred對象的運(yùn)行狀態(tài)為"已完成"希俩,從而立即觸發(fā)done()方法吊宋。
(6)deferred.reject() 這個方法與deferred.resolve()正好相反颜武,調(diào)用后將deferred對象的運(yùn)行狀態(tài)變?yōu)?已失敗"璃搜,從而立即觸發(fā)fail()方法拖吼。
(7) $.when() 為多個操作指定回調(diào)函數(shù)这吻。
除了這些方法以外吊档,deferred對象還有二個重要方法,上面的教程中沒有涉及到唾糯。
〉∨稹(8)deferred.then()
有時為了省事,可以把done()和fail()合在一起寫移怯,這就是then()方法香璃。
$.when($.ajax( "/main.php" ))
**.then(successFunc, failureFunc );**
如果then()有兩個參數(shù),那么第一個參數(shù)是done()方法的回調(diào)函數(shù)芋酌,第二個參數(shù)是fail()方法的回調(diào)方法增显。如果then()只有一個參數(shù),那么等同于done()脐帝。
⊥啤(9)deferred.always()
這個方法也是用來指定回調(diào)函數(shù)的,它的作用是堵腹,不管調(diào)用的是deferred.resolve()還是deferred.reject()炸站,最后總是執(zhí)行。
$.ajax( "test.html" )
.always( function() { alert("已執(zhí)行疚顷!");} );
其實(shí) 旱易,我只是個搬運(yùn)工,主要還是阮一峰大神寫的好
參考文檔
deferred: http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html
promise:
http://es6.ruanyifeng.com/#docs/promise