JQuery Deferred 对象剖析

JQuery 中利用 Deferred 对象提供类似 ES2016(aka. es7) 中 Promise 的功能。
JQuery 中的 AJAX 请求函数返回的就是 Deferred 对象。
通过使用 Defered 让异步调用更加可读,实现高级的用法(指定多个回调函数/等待多个 AJAX 请求)。

  • JQuery中传统的AJAX请求
$.getJSON('url', data=>{
    // 处理返回的JSON数据
    console.log(data);
}, err=>{
    // 处理出错信息
    console.log(err.status);
})
  • 使用 Deferred 的AJAX请求
$.getJSON('url').done(data=>{
    console.log(data);
}).fail(err=>{
    console.log(err.status);
});

这篇文章会着重分析 Deferred/Promise 的状态变化过程,并讲述 Deferred 对象在实际编码中的应用。

创建 Deferred

用户可以使用 Deferred 函数来创建 Deferred 对象

var d1 = $.Deferred();
console.log(d1.state());

Deferred 对象的状态

Deferred 对象有以下三种状态

  • pending
  • rejected
  • resolved

Deferred 对象可以从pending状态转换到rejected状态或者resolved.
这种状态的转换是单向的,也就是说一旦Deferred对象的状态是 rejected/resolved, 对象的状态将不可变。

得到 Deferred 对象的状态

JQuery 提供了三个函数来查看/修改Deferred对象的状态:

  • deferred.state(), 返回一个字符串表示当前 Deferred 对象的状态
  • deferred.reject(), 将对象的状态设置为 rejected
  • deferred.resolve(), 将对象的状态设置为 resolved

下面的代码定义了两个 Deferred 对象,并分别使用 resolve/reject 改变他们的状态

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 回调函数

JQuery 提供了方法来指定当 Deferred 对象状态发生变化时的回调函数

指定回调函数

使用 then 方法

then 方法接受两个函数作为参数,当对象的状态变成 resolved, 第一个函数会被调用。
当对象的状态变成 rejected, 第二个函数会被调用。

第一个函数接受一个参数,该参数是 defered.resolve 函数被调用时传递的第一个参数。
第二个函数接受一个参数,该参数是 defered.reject 函数被调用时传递的第一个参数。

  • 使用 then 和 resolve 方法
var d1 = $.Deferred();

// 注册回调函数
d1.then(function(data){
    console.log('First function: ' + data.state);
}, function(err){
    console.log('Second function: ' + err);
});

// 做一些耗时的操作

// 改变 deferred 对象状态为 resolved
// 回调函数将被调用,打印信息:First function: successed
d1.resolve({state: 'successed'});
  • 使用 then 和 reject 方法
var d2 = $.Deferred();

// 注册回调函数
d2.then(function(data){
    console.log('First function: ' + data.state);
}, function(err){
    console.log('Second function: ' + err.state);
});

// 改变 deferred 对象状态为 rejected
// 回调函数将被调用,打印信息:Second function: failed
d2.reject({state: 'failed'});

使用 done 方法

使用 done 方法可以指定当 deferred 对象状态变成 resolved 时调用的函数

var deferred = $.Deferred();
deferred.done(function(data){
    console.log('Done: ' + data.state);
});

deferred.resolve({state: 'successed'});

使用 fail 方法

使用 fail 方法可以指定当 deferred 对象状态变成 rejected 时调用的函数

var deferred = $.Deferred();
deferred.fail(function(err){
    console.log('Fail: ' + err.state);
})
deferred.resolve({state: 'failed'});

使用 always 方法

always 方法接受一个函数作为参数,只要 Deferred 对象的状态发生变化,该函数都会被调用。

链式调用

因为 then/done/fail 函数返回的仍然是类 Deferred 对象,所以我们可以使用他们来指定多个回调函数.
下面这个例子用 done/fail 来指定多个

var deferred = $.Deferred();
deferred.done(data=>{
    // Do something
}).done(data=>){
    // Do something
}.fail(err=>{
    // Handle the error
}).always(()=>{
    // Clean the environment
})

Promise 对象

JQuery 还提供了 Promise 对象,可以通过 Deferred 对象的 promise 方法来获取该对象的 Promise 对象。=
Promise 对象有以下特点:

  • Promise 对象没有 reject/resolve 方法
  • Promise 对象的状态跟 Deferred 对象保持一致
  • Promise 对象通过 state() 方法获取跟它绑定的 Deferred 的状态
  • Proimse 对象也可以使用 then/done/fail 方法来指定回调函数
  • Promise 可以调用 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 对象的 promis() 方法返回的是它自己

then 和 done/fail 的差异

done/fail 返回的是 Deferred 对象自身
then 方法返回的是一个新的 Promise 对象

使用 done/fail 方法返回 Deferred 对象

var d1 = $.Deferred();
var d2 = d1.done();
var d3 = d1.fail();

console.log(d1 === d2); // true
console.log(d1 === d3); // true

使用 then 方法返回 Promise 对象

使用 then 方法我们需要明白的几个关键点是:

方法返回的是 Promise 对象,该对象没有 resolve/reject 方法。

Deferred 对象的 then 方法, 会创建一个新的 Deferred 对象,并返回新 Deferred 对象的 Promise 对象。
而且 then 方法返回的对象 跟 Deferred 对象的 Promise 对象不相等, 多次调用 then 对象会产生多个 Deferred 对象。
下面的例子对比了多次调用 then 方法产生的 Promise 对象

var d1 = $.Deferred();
var p2 = d1.then();  // 调用 then 方法返回一个 Promise 对象
var p3 = d1.then();  // 调用 then 方法返回一个新的 Promise 对象

console.log('reject' in d1);       // false, 查看 then 方法返回的对象中是否有 reject 方法
console.log('reject' in p2);       // false, 查看 then 方法返回的对象中是否有 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 对象的状态发生改变后,then 方法产生的 Promise 对象的状态不会立即发生变化

Deferred 对象状态发生变化后, 等待一段时间后 then 方法产生的 Promise 对象的状态才会发生相应的变化

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 的状态变成了 rejected

发生了什么

Deferred 对象的状态发生改变后,then 方法产生的 Promise 对象的状态并没有立即发生变化, 而是等待了一段时间后才改变。
这段时间内,发生了什么那?
我们以调用 Deferred 对象的 resolve 方法作为例子来说明。 调用 reject 方法的情况与此类似。

  • 首先假设我们构造了Deferred 对象 d1
  • 然后调用 then 方法,并且传入两个函数作为参数 fun1, fun2: var p2 = d1.then(fun1, fun2)
  • 调用 d1.resolve(data) 方法将 d1 的状态设置为 resolved, 此时d1 的状态是 resolved, p2 的状态是 pending
  • fun1 会被调用, 参数为 d1.resolve 方法的参数: var new_data = fun1(data)
  • 假设 p2 对应的 Deferred 对象是 d2.
  • d2 的 resolve 方法会被调用, 参数为 fun1 的返回值: d2.resolve(new_data)
  • p2 的状态变为 resolved
  • p2 的回调函数会被调用

下面的代码展示了 then 方法产生的 Promise 对象的状态变化。以及如何给回调函数传递参数

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
*/

在网页中使用 Deferred

自定义函数

明白了 Deferred 的原理,我们就可以使用 Deferred.
下面一段代码定义了一个函数, 在函数中定义了一些耗时的操作。
函数返回 Promise 对象, 可以使用 done/fail/then 注册回调函数


function say_hello(){
    // 创建 Deferred 对象
    var deferred = $.Deferred();

    // 做一些耗时的操作,操作完成后调用 resolve 或者 reject 函数结束。
    // 我们用 setTimeout 函数模拟一段耗时操作:
    // 等待五秒钟后,调用 Deferred 的 resolve 方法来改变状态
    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 对象, 防止外界对 Deferred 对象的状态进行改变
}

// 调用 say_hello函数,并使用 done/fail/then 方法注册回调函数
say_hello().done(msg=>{
    console.log(msg);
});

$.when

跟 ES2016 中 Prmomise.all 函数类似。
JQuery 提供了 when 函数, 它可以接受多个 Deferred/Promise 对象作为参数。并返回一个 Promise 对象。
新的 Promise 对象会等待参数中所有的对象状态变为 resolved/reject。
如果参数中任何一个对象的状态变为 rejected, 那么 Promise 对象的状态变为 rejected。 否则变为 resolved。

// 创建一个函数,如果参数大于500, 则将内置的 Deferred 对象状态变为 resolved
// 如果参数小于500, 则将内置的 Deferred 对象状态变为 rejected
function get_promise(delay){
    // 创建 Deferred 对象
    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 对象
}

// 如果任一参数状态转变为 rejected, when 函数产生的 promise 对象状态会理解变为 rejected。
// 并将第一个 Deferred 对象的错误信息传递给回调函数
$.when(get_promise(800), get_promise(100), get_promise(300)).fail(error=>{
    console.log(error); // 1
});
// 否则 when 函数会等待所有的 Deferred 对象状态变为 resolved, 并将所有 Deferred 对象的返回值依次传递给回调函数
$.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]
});





转载请注明出处:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容