JQuery 中利用 Deferred 對(duì)象提供類似 ES2016(aka. es7) 中 Promise 的功能。
JQuery 中的 AJAX 請(qǐng)求函數(shù)返回的就是 Deferred 對(duì)象。
通過(guò)使用 Defered 讓異步調(diào)用更加可讀,實(shí)現(xiàn)高級(jí)的用法(指定多個(gè)回調(diào)函數(shù)/等待多個(gè) AJAX 請(qǐng)求)。
- JQuery中傳統(tǒng)的AJAX請(qǐng)求
$.getJSON('url', data=>{
// 處理返回的JSON數(shù)據(jù)
console.log(data);
}, err=>{
// 處理出錯(cuò)信息
console.log(err.status);
})
- 使用 Deferred 的AJAX請(qǐng)求
$.getJSON('url').done(data=>{
console.log(data);
}).fail(err=>{
console.log(err.status);
});
這篇文章會(huì)著重分析 Deferred/Promise 的狀態(tài)變化過(guò)程,并講述 Deferred 對(duì)象在實(shí)際編碼中的應(yīng)用。
創(chuàng)建 Deferred
用戶可以使用 Deferred 函數(shù)來(lái)創(chuàng)建 Deferred 對(duì)象
var d1 = $.Deferred();
console.log(d1.state());
Deferred 對(duì)象的狀態(tài)
Deferred 對(duì)象有以下三種狀態(tài)
- pending
- rejected
- resolved
Deferred 對(duì)象可以從pending狀態(tài)轉(zhuǎn)換到rejected狀態(tài)或者resolved.
這種狀態(tài)的轉(zhuǎn)換是單向的,也就是說(shuō)一旦Deferred對(duì)象的狀態(tài)是 rejected/resolved, 對(duì)象的狀態(tài)將不可變。
得到 Deferred 對(duì)象的狀態(tài)
JQuery 提供了三個(gè)函數(shù)來(lái)查看/修改Deferred對(duì)象的狀態(tài):
-
deferred.state(), 返回一個(gè)字符串表示當(dāng)前 Deferred 對(duì)象的狀態(tài) -
deferred.reject(), 將對(duì)象的狀態(tài)設(shè)置為 rejected -
deferred.resolve(), 將對(duì)象的狀態(tài)設(shè)置為 resolved
下面的代碼定義了兩個(gè) Deferred 對(duì)象,并分別使用 resolve/reject 改變他們的狀態(tài)
let d1 = $.Deferred();
console.log(d1.state()); // "pending"
d1.resolve();
console.log(d1.state()); // "resolved"
let d2 = $.Deferred();
console.log(d2.state()); // "pending"
d2.reject();
console.log(d2.state()); // "rejected"
Deferred 回調(diào)函數(shù)
JQuery 提供了方法來(lái)指定當(dāng) Deferred 對(duì)象狀態(tài)發(fā)生變化時(shí)的回調(diào)函數(shù)
指定回調(diào)函數(shù)
使用 then 方法
then 方法接受兩個(gè)函數(shù)作為參數(shù),當(dāng)對(duì)象的狀態(tài)變成 resolved, 第一個(gè)函數(shù)會(huì)被調(diào)用。
當(dāng)對(duì)象的狀態(tài)變成 rejected, 第二個(gè)函數(shù)會(huì)被調(diào)用。第一個(gè)函數(shù)接受一個(gè)參數(shù),該參數(shù)是
defered.resolve函數(shù)被調(diào)用時(shí)傳遞的第一個(gè)參數(shù)。
第二個(gè)函數(shù)接受一個(gè)參數(shù),該參數(shù)是defered.reject函數(shù)被調(diào)用時(shí)傳遞的第一個(gè)參數(shù)。
- 使用 then 和 resolve 方法
var d1 = $.Deferred();
// 注冊(cè)回調(diào)函數(shù)
d1.then(function(data){
console.log('First function: ' + data.state);
}, function(err){
console.log('Second function: ' + err);
});
// 做一些耗時(shí)的操作
// 改變 deferred 對(duì)象狀態(tài)為 resolved
// 回調(diào)函數(shù)將被調(diào)用,打印信息:First function: successed
d1.resolve({state: 'successed'});
- 使用 then 和 reject 方法
var d2 = $.Deferred();
// 注冊(cè)回調(diào)函數(shù)
d2.then(function(data){
console.log('First function: ' + data.state);
}, function(err){
console.log('Second function: ' + err.state);
});
// 改變 deferred 對(duì)象狀態(tài)為 rejected
// 回調(diào)函數(shù)將被調(diào)用,打印信息:Second function: failed
d2.reject({state: 'failed'});
使用 done 方法
使用 done 方法可以指定當(dāng) deferred 對(duì)象狀態(tài)變成 resolved 時(shí)調(diào)用的函數(shù)
var deferred = $.Deferred();
deferred.done(function(data){
console.log('Done: ' + data.state);
});
deferred.resolve({state: 'successed'});
使用 fail 方法
使用 fail 方法可以指定當(dāng) deferred 對(duì)象狀態(tài)變成 rejected 時(shí)調(diào)用的函數(shù)
var deferred = $.Deferred();
deferred.fail(function(err){
console.log('Fail: ' + err.state);
})
deferred.resolve({state: 'failed'});
使用 always 方法
always 方法接受一個(gè)函數(shù)作為參數(shù),只要 Deferred 對(duì)象的狀態(tài)發(fā)生變化,該函數(shù)都會(huì)被調(diào)用。
鏈?zhǔn)秸{(diào)用
因?yàn)?then/done/fail 函數(shù)返回的仍然是類 Deferred 對(duì)象,所以我們可以使用他們來(lái)指定多個(gè)回調(diào)函數(shù).
下面這個(gè)例子用 done/fail 來(lái)指定多個(gè)
var deferred = $.Deferred();
deferred.done(data=>{
// Do something
}).done(data=>){
// Do something
}.fail(err=>{
// Handle the error
}).always(()=>{
// Clean the environment
})
Promise 對(duì)象
JQuery 還提供了 Promise 對(duì)象,可以通過(guò) Deferred 對(duì)象的 promise 方法來(lái)獲取該對(duì)象的 Promise 對(duì)象。=
Promise 對(duì)象有以下特點(diǎn):
- Promise 對(duì)象沒(méi)有
reject/resolve方法 - Promise 對(duì)象的狀態(tài)跟 Deferred 對(duì)象保持一致
- Promise 對(duì)象通過(guò)
state()方法獲取跟它綁定的 Deferred 的狀態(tài) - Proimse 對(duì)象也可以使用
then/done/fail方法來(lái)指定回調(diào)函數(shù) - Promise 可以調(diào)用 promise 方法獲取它自身
var deferred = $.Deferred();
var promise = deferred.promise();
deferred.reject();
console.log(deferred.state()); // rejected
console.log(promise.state()); // rejected
console.log(promise.promise() === promise); // true, Promise 對(duì)象的 promis() 方法返回的是它自己
then 和 done/fail 的差異
done/fail 返回的是 Deferred 對(duì)象自身
then 方法返回的是一個(gè)新的 Promise 對(duì)象
使用 done/fail 方法返回 Deferred 對(duì)象
var d1 = $.Deferred();
var d2 = d1.done();
var d3 = d1.fail();
console.log(d1 === d2); // true
console.log(d1 === d3); // true
使用 then 方法返回 Promise 對(duì)象
使用 then 方法我們需要明白的幾個(gè)關(guān)鍵點(diǎn)是:
方法返回的是 Promise 對(duì)象,該對(duì)象沒(méi)有 resolve/reject 方法。
Deferred 對(duì)象的
then方法, 會(huì)創(chuàng)建一個(gè)新的 Deferred 對(duì)象,并返回新 Deferred 對(duì)象的 Promise 對(duì)象。
而且then方法返回的對(duì)象 跟 Deferred 對(duì)象的 Promise 對(duì)象不相等, 多次調(diào)用 then 對(duì)象會(huì)產(chǎn)生多個(gè) Deferred 對(duì)象。
下面的例子對(duì)比了多次調(diào)用 then 方法產(chǎn)生的 Promise 對(duì)象
var d1 = $.Deferred();
var p2 = d1.then(); // 調(diào)用 then 方法返回一個(gè) Promise 對(duì)象
var p3 = d1.then(); // 調(diào)用 then 方法返回一個(gè)新的 Promise 對(duì)象
console.log('reject' in d1); // false, 查看 then 方法返回的對(duì)象中是否有 reject 方法
console.log('reject' in p2); // false, 查看 then 方法返回的對(duì)象中是否有 reject 方法
console.log(p2 === d1); // false, 檢查 d1 是否與 p2 相等
console.log(p2 === d1.promise()); // false, 查看 d1 的 promise 是否與 p2 相等
console.log(p2 === p3); // false, p2 和 p3 的值不同
Deferred 對(duì)象的狀態(tài)發(fā)生改變后,then 方法產(chǎn)生的 Promise 對(duì)象的狀態(tài)不會(huì)立即發(fā)生變化
Deferred 對(duì)象狀態(tài)發(fā)生變化后, 等待一段時(shí)間后 then 方法產(chǎn)生的 Promise 對(duì)象的狀態(tài)才會(huì)發(fā)生相應(yīng)的變化
var deferred = $.Deferred();
var new_promise = deferred.then();
deferred.reject('reject')
console.log(`d1 state: ${deferred.state()}`); // rejected
console.log(`new_promise state: ${new_promise.state()}`); // pending
setTimeout(`console.log("new_promise state after 100 miliseconds: ${new_promise.state()}")`, 100); // 100 毫秒后, new_promise 的狀態(tài)變成了 rejected
發(fā)生了什么
Deferred 對(duì)象的狀態(tài)發(fā)生改變后,then 方法產(chǎn)生的 Promise 對(duì)象的狀態(tài)并沒(méi)有立即發(fā)生變化, 而是等待了一段時(shí)間后才改變。
這段時(shí)間內(nèi),發(fā)生了什么那?
我們以調(diào)用 Deferred 對(duì)象的 resolve 方法作為例子來(lái)說(shuō)明。 調(diào)用 reject 方法的情況與此類似。
- 首先假設(shè)我們構(gòu)造了Deferred 對(duì)象 d1
- 然后調(diào)用 then 方法,并且傳入兩個(gè)函數(shù)作為參數(shù) fun1, fun2:
var p2 = d1.then(fun1, fun2) - 調(diào)用
d1.resolve(data)方法將 d1 的狀態(tài)設(shè)置為 resolved, 此時(shí)d1 的狀態(tài)是 resolved, p2 的狀態(tài)是 pending - fun1 會(huì)被調(diào)用, 參數(shù)為
d1.resolve方法的參數(shù):var new_data = fun1(data) - 假設(shè) p2 對(duì)應(yīng)的 Deferred 對(duì)象是 d2.
- d2 的 resolve 方法會(huì)被調(diào)用, 參數(shù)為 fun1 的返回值:
d2.resolve(new_data) - p2 的狀態(tài)變?yōu)?resolved
- p2 的回調(diào)函數(shù)會(huì)被調(diào)用
下面的代碼展示了 then 方法產(chǎn)生的 Promise 對(duì)象的狀態(tài)變化。以及如何給回調(diào)函數(shù)傳遞參數(shù)
var d1 = $.Deferred();
function fun1(data){
console.log(`p2 state in fun1: ${p2.state()}`);
console.log(`data in fun1: ${data}`);
return data * 3;
}
function fun2(error){
return 'new data from fun2';
}
var p2 = d1.then(fun1, fun2);
p2.done(data=>{
console.log(`p2 state in done: ${p2.state()}`);
console.log(`data in done: ${data}`);
});
d1.resolve(10);
/* 屏幕輸出為
p2 state in fun1: pending
data in fun1: 10
p2 state in done: resolved
data in done: 30
*/
在網(wǎng)頁(yè)中使用 Deferred
自定義函數(shù)
明白了 Deferred 的原理,我們就可以使用 Deferred.
下面一段代碼定義了一個(gè)函數(shù), 在函數(shù)中定義了一些耗時(shí)的操作。
函數(shù)返回 Promise 對(duì)象, 可以使用 done/fail/then 注冊(cè)回調(diào)函數(shù)
function say_hello(){
// 創(chuàng)建 Deferred 對(duì)象
var deferred = $.Deferred();
// 做一些耗時(shí)的操作,操作完成后調(diào)用 resolve 或者 reject 函數(shù)結(jié)束。
// 我們用 setTimeout 函數(shù)模擬一段耗時(shí)操作:
// 等待五秒鐘后,調(diào)用 Deferred 的 resolve 方法來(lái)改變狀態(tài)
setTimeout(deferred.resolve.bind(deferred, 'hello world'), 5000);
// 也可以使用 AJAX 操作
/*
$.getJSON('/api/names').done(data=>{
if(data.state == 'successed'){
deferred.resolve(data);
}else{
deferred.reject(data);
}
});
*/
return deferred.promise(); // 返回 promise 對(duì)象, 防止外界對(duì) Deferred 對(duì)象的狀態(tài)進(jìn)行改變
}
// 調(diào)用 say_hello函數(shù),并使用 done/fail/then 方法注冊(cè)回調(diào)函數(shù)
say_hello().done(msg=>{
console.log(msg);
});
$.when
跟 ES2016 中 Prmomise.all 函數(shù)類似。
JQuery 提供了 when 函數(shù), 它可以接受多個(gè) Deferred/Promise 對(duì)象作為參數(shù)。并返回一個(gè) Promise 對(duì)象。
新的 Promise 對(duì)象會(huì)等待參數(shù)中所有的對(duì)象狀態(tài)變?yōu)?resolved/reject。
如果參數(shù)中任何一個(gè)對(duì)象的狀態(tài)變?yōu)?rejected, 那么 Promise 對(duì)象的狀態(tài)變?yōu)?rejected。 否則變?yōu)?resolved。
// 創(chuàng)建一個(gè)函數(shù),如果參數(shù)大于500, 則將內(nèi)置的 Deferred 對(duì)象狀態(tài)變?yōu)?resolved
// 如果參數(shù)小于500, 則將內(nèi)置的 Deferred 對(duì)象狀態(tài)變?yōu)?rejected
function get_promise(delay){
// 創(chuàng)建 Deferred 對(duì)象
var deferred = $.Deferred();
if(delay > 500){
setTimeout(deferred.resolve.bind(deferred, delay/100), delay);
}else{
setTimeout(deferred.reject.bind(deferred, delay/100), delay);
}
return deferred.promise(); // 返回 promise 對(duì)象
}
// 如果任一參數(shù)狀態(tài)轉(zhuǎn)變?yōu)?rejected, when 函數(shù)產(chǎn)生的 promise 對(duì)象狀態(tài)會(huì)理解變?yōu)?rejected。
// 并將第一個(gè) Deferred 對(duì)象的錯(cuò)誤信息傳遞給回調(diào)函數(shù)
$.when(get_promise(800), get_promise(100), get_promise(300)).fail(error=>{
console.log(error); // 1
});
// 否則 when 函數(shù)會(huì)等待所有的 Deferred 對(duì)象狀態(tài)變?yōu)?resolved, 并將所有 Deferred 對(duì)象的返回值依次傳遞給回調(diào)函數(shù)
$.when(get_promise(900), get_promise(600), get_promise(1000)).done((d1, d2, d3)=>{
console.log(d1); // 9
console.log(d2); // 6
console.log(d3); // 10
});
$.when(get_promise(800), get_promise(900), get_promise(1000)).done((...datas)=>{
console.log(datas); // [8, 9, 10]
});
轉(zhuǎn)載請(qǐng)注明出處: