JQuery Deferred 對(duì)象剖析

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)注明出處:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 00、前言Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大。它由社區(qū)...
    夜幕小草閱讀 2,227評(píng)論 0 12
  • Promise 的含義 一句話概括一下promise的作用:可以將異步操作以同步操作的流程表達(dá)出來(lái),避免了層層嵌套...
    雪萌萌萌閱讀 5,661評(píng)論 0 7
  • Promise的含義: ??Promise是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和...
    呼呼哥閱讀 2,278評(píng)論 0 16
  • 本文適用的讀者 本文寫給有一定Promise使用經(jīng)驗(yàn)的人,如果你還沒(méi)有使用過(guò)Promise,這篇文章可能不適合你,...
    HZ充電大喵閱讀 7,458評(píng)論 6 19
  • 我對(duì)金錢的第一次概念,可以說(shuō)是20年前的事了,也就是在家上學(xué)的時(shí)光。作為一個(gè)80后的我來(lái)說(shuō),當(dāng)時(shí)家庭條件很差,羨慕...
    生活隨筆錄閱讀 340評(píng)論 0 0

友情鏈接更多精彩內(nèi)容