jQuery的Deferred对象概述,jQuery之Deferred对象详明

jQuery的Deferred对象概述

2017/03/02 · CSS

本文由 伯乐在线 - HansDo 翻译,叙帝利 校稿。未经许可,禁止转载!
英文出处:Aurelio De Rosa。欢迎加入翻译组。

很久以来,JavaScript 开发者们习惯用回调函数的方式来执行一些任务。最常见的例子就是利用 addEventListener()函数来添加一个回调函数, 用来在指定的事件(如 clickkeypress)被触发时,执行一系列的操作。回调函数简单有效——在逻辑并不复杂的时候。遗憾的是,一旦页面的复杂度增加,而你因此需要执行很多并行或串行的异步操作时,这些回调函数会让你的代码难以维护。

ECMAScript 2015(又名 ECMAScript 6) 引入了一个原生的方法来解决这类问题:promises。如果你还不清楚 promise 是什么,可以阅读这篇文章《Javascript Promise概述》。jQuery 则提供了独具一格的另一种 promises,叫做 Deferred 对象。而且 Deferred 对象的引入时间要比 ECMAScript 引入 promise 早了好几年。在这篇文章里,我会介绍 Deferred 对象和它试图解决的问题是什么。

deferred对象是jQuery对Promises接口的实现。它是非同步操作的通用接口,可以被看作是一个等待完成的任务,开发者通过一些通过的接口对其进行设置。事实上,它扮演代理人(proxy)的角色,将那些非同步操作包装成具有某些统一特性的对象,典型例子就是Ajax操作、网页动画、web worker等等。

读jQuery源码 - Deferred

Deferred首次出现在jQuery 1.5中,在jQuery 1.8之后被改写,它的出现抹平了javascript中的大量回调产生的金字塔,提供了异步编程的能力,它主要服役于jQuery.ajax。

 

Deferred就是让一组函数在合适的时机执行,在成功时候执行成功的函数系列,在失败的时候执行失败的函数系列,这就是Deferred的本质。简单的说,模型上可以规划为两个数组来承接不同状态的函数——数组resolve里的函数列表在成功状态下触发,reject中的函数在失败状态下触发。

 

本文原创于linkFly,原文地址。

 

这篇文章主要分为以下知识,和上一篇博文《读jQuery源码之 - Callbacks》关联。

 

什么是Deferred

jQuery.Deferred的Promise

Deferred的模型与工作原理

jQuery.Deferred的实现

jQuery.Deferred的then()

什么是Deferred

初窥Deferred

Deferred本身就是承接一组函数,在异步中执行这组函数,过去的我们是这样编写异步代码的:

 

 

复制代码

       var resolve = function () {

                console.log('成功');

            },

            //定义一个失败状态下运行的函数

            reject = function () {

                console.log('失败');

            };

            //模拟服务器请求,正在等待服务器响应ing.....

            setTimeout(function (status) {

                if (status === 200) resolve();

                else reject();

            }, 1000);

复制代码

如果用Deferred那么上面的代码则应该是下面这个样子:

 

 

复制代码

        var deferred = new Deferred();

            //先将成功和失败的函数委托到Deferred

            deferred.resolve(function () {

                console.log('成功');

            }).reject(function () {

                console.log('失败');

            }).resolve(function () {

                console.log('我还想再追加一个成功的函数');

            });

            //当改变状态的时候,会自动触发成或者失败的函数

            setTimeout(function (status) {

                if (status === 200) deferred.resolve();

                else deferred.reject();

            });    

复制代码

Deferred有点Callbacks的特质,不过是Callbacks的逼格提升版(异步定制版)。在Callbacks的基础上提升了对于异步函数的管理——本身的使用和Callbacks一样:承接一组函数,触发执行。

 

Deferred主要服役于jQuery.ajax(),使用了Deferred的ajax代码如下:

 

复制代码

            //Deferred的应用(ajax)

            $.ajax('demo.html').done(function () {

                console.log('ajax成功');

            }).fail(function () {

                console.log('ajax失败');

            }).done(function () {

                console.log('ajax成功,追加一条函数处理我们自己的事情...');

            });    

复制代码

jQuery把Deferred对象封装到ajax中,jQuery.ajax()中,jQuery维护ajax请求的发起到接收,而使用jQuery的开发者,只关注ajax的结果即可,众所周知,ajax是异步的,而我们这些成功后(失败后)要执行的函数,从代码的层面上,是线性的编写的——正是Deferred提供了这样异步编程的能力。

 

回调函数的定义(委托)和回调函数的执行

Deferred切割了回调函数和执行时机两个概念。就是把回调函数的定义和回调函数的执行这两个概念给分离开,同一时间专注一个概念,这样代码就能够线性的编写下去,Deferred主要应用于这种回调函数的多层嵌套,而这种情况多发生于异步(当然它也确实是为异步量身打造),所以就叫Deferred——让你异步的代码,看起来跟像同步执行一样。当然它并不局限与异步。

 

Deferred在Callbacks基础上做的二次封装,它封装了一组状态,每组状态对应一个Callbacks对象。我们还是说的再简单通俗一点吧:Deferred主要有三个状态作为工作标志:成功、失败、无状态。成功失败还好点,这个“无状态”是个神马意思??

 

Deferred本身就是根据状态来触发的,成功状态下触发成功状态的函数,失败状态下触发失败状态的函数,最后这个无状态就是:既不成功,也不失败,但是每次要触发相应的函数——用于文件上传,在文件上传的ajax中,要和服务器一直保持请求,每次请求既不代表成功也不代表失败,那么这个无状态就是最好的标志。

 

Deferred本质上的实现就是用数组专门用来存放对应状态的函数,然后循环执行。就是有三个数组:代表成功状态下执行的resolve函数数组和代表失败状态下执行的reject函数数组, 还有一个每次触发都会执行的progress数组。

 

jQuery.Deferred的Promise

jQuery.Deferred里面实现了Promise/A规范。

 

jQuery中,jQuery.Deferred其实本身就已经实现了Promise/A规范,并且还扩充了一套很实用的API,但是jQuery.Deferred对象中又包含着一个Promise对象,这个对象和Promise/A基本没有关联,它是切掉丁丁的jQuery.Deferred(阉割版),只有上面所说的回调函数的定义这一部分API,并没有回调函数的执行这一部分的API,这么做是因为可以在Ajax中把回调函数的执行给封闭起来(jQuery自己维护这部分),而使用jQuery的开发者则使用回调函数的定义这部分——实现一个恰到好处的观察者模式。

 

Deferred工作在Ajax更深层次的地方,而外层只需要根据相关结果做出对应的操作即可,即Promise对象。例如Ajax请求成功后,Deferred工作在Ajax内层(发送请求,接收请求) ,Promise对外的API接收了对应行为的函数,当内层Ajax请求成功的时候,通过Deferred标识状态为成功,那么这些承接的函数都会执行——Deferred主要应用于这样的工作场景。而因为对外开放的是Promise,它并不具备回调函数的执行这部分代码,而这部分,是被内层Deferred维护的。

 

这就类似你们大boss要你办一件事,并提前给了三种情形的解决方案,分别表示:这件事处理成功了之后该怎么做,处理失败了又该怎么做,处理中该怎么做。大boss给的解决方案,就是回调函数的定义,而你在这件事得到结果后针对不同的情况进行处理,就是回调函数的执行,当然,大boss也可以自己来处理这件事。并在得到结果后自己针对不同的情况进行处理,这就是Deferred和Promise对象的关系于用途。

 

Deferred的模型与工作原理

上面叽里呱啦说了一大堆,仍然没懂?木关系,我们直接来看模型看API就能确定这玩意儿到底是什么了,jQuery.Deferred有如下API:

 

API 隶属对象 描述 实现

done(function[,function...]) Deferred&Promise 添加一个或多个表示成功的函数到Deferred对象中,与Deferred.resolve()方法对应。 内部原型是Callbacks对象,该方法直接引用Callbacks.add()

fail(function[,function...]) Deferred&Promise 添加一个或多个表示失败的函数到Deferred对象中,与Deferred.reject()方法对应。 同上

progress(function[,function...]) Deferred&Promise 添加一个或多个表示无状态的函数到Deferred对象中,与Deferred.notify()方法对应,每次执行Deferred.notify()都会执行委托的回调函数,而done()、fail()方法中委托的回调函数都是一次性的。 同上

resolve([args]) Deferred 触发成功系列函数(通过Deferred.done()追加的函数),注意每次执行这些函数之后都会被销毁。 内部原型直接引用了Callbacks对象的fireWith()方法。

reject([args]) Deferred 触发失败系列函数(通过Deferred.fail()追加的函数),注意每次执行这些函数之后都会被销毁。 同上

notify([args]) Deferred 触发无状态系列函数(通过Deferred.progress()追加的函数),注意每次执行这些函数之后都会被销毁。 同上

promise([Object]) Deferred&Promise 无参的情况下返回Promise对象,有参数的情况下为参数Object扩展Promise行为。 阉割版的Deferred,内部先定义了Promise的基础API,在此基础上扩展了Deferred,就是用有参的promise()将promise的行为扩展到Deferred上的。

state() Deferred&Promise 返回当前状态的字符串:pending(尚未执行)、resolved(已成功)、rejected(已失败)、undefined(无状态,未定义) 执行相应函数的时候标识一下状态就可以了。

then(doneCallbacks[,failCallbacks[,progressCallbacks]]) Deferred&Promise 在jQuery 1.8以后被重写,委托最多三组函数到Deferred对象中,分别表示:成功、失败、无状态下执行的函数,从使用上来说,是Deferred.done()、Deferred.fail()、Deferred.progress()的简写版——然而,本质上并非如此,then方法是单独实现的——它返回一个全新的Promise对象,它连接了链式回调中的参数,让每个函数都可以与上一层、下一层函数通信,详情请见《jQuery.Deferred的then()》小节。 内部的实现较为复杂,创建了一个全新的Deferred对象(与Deferred.done())系列函数完全不同,每一次在同一个Deferred对象上链式调用then()都建立了深层的嵌套,并且通过回调函数的返回值与下一层进行通信。

always(function[,function]) Deferred&Promise 接收两个函数,分别表示成功、失败执行的函数,这才是正统的使用Deferred.done()、Deferred.fail()实现的API。 内部调用Deferred.done()、Deferred.fail()实现

other Deferred&Promise 还有一些其他的API无关痛痒啊,基本都是在上面的API基础上扩展的,so easy~~~

从上面的API里可以看见,Promise对象就是切掉小丁丁版本的Deferred,只有回调函数的定义(done/fail/progress)API,没有回调函数的执行(resolve/reject/notify)API。下面有美图一张...

 

 

 

基础部分从代码的表现上(API的使用上)是这些:

       (function () {

            //Deferred的done/resolve

            var deferred = $.Deferred();

            deferred.done(function (state) {

                console.log(state); //write 1

            });

            resolve.resolve(1);

        } ());

        ~function () {

            //Deferred的fail/reject

            var deferred = $.Deferred();

            deferred.fail(function (state) {

                console.log(state); //write 2

            });

            resolve.reject(2);

        } ();

        !function () {

            //Deferred的progress/notify

            /*

            progress/notify对应的是“无状态”的状态

            它表示一个既不表示成功,也不表示失败的状态

            它每一次的触发(notify)都会执行progress里面的函数

            和resolve、reject不同,通过progress委托的函数,每次notify都不会被清空

            它可以反复被执行,用于会话保持

            */

            var deferred = $.Deferred();

            progress.progress(function (state) {

                console.log(state); //write 2

            });

            setTimeout(function () {

                //每隔1s反复执行

                resolve.notify(2);

            }, 1000);

        } ();

复制代码

jQuery.Deferred的实现

上面说了一大堆概念啊神马的可能好多人都觉得这他瞄的什么玩意儿,直接给个痛快咱们看代码吧。

 

那就亮好我们的12氪钛金硬化写轮防暴 12透视*2000狗眼:

 

结构:

 

复制代码

//jQuery.Deferred结构代码

        jQuery.extend({

            Deferred: function (func) {

                var tuples = [

                    ["resolve", "done", jQuery.Callbacks("once memory"), "resolved"],

                    ["reject", "fail", jQuery.Callbacks("once memory"), "rejected"],

                    ["notify", "progress", jQuery.Callbacks("memory")]

                ],

                state = "pending",

                promise = {

                    state: function () {

                        return state;

                    },

                    always: function () {

                        //直接调用

                        deferred.done(arguments).fail(arguments);

                        return this;

                    },

                    //then方法稍后解读

                    then: function ( /* fnDone, fnFail, fnProgress */) { },

                    promise: function (obj) {

                        return obj != null ? jQuery.extend(obj, promise) : promise;

                    }

                },

                deferred = {};

                //过去pipe,现在的then

                promise.pipe = promise.then;

                jQuery.each(tuples, function (i, tuple) {

                    var list = tuple[2],

                    stateString = tuple[3];

                    promise[tuple[1]] = list.add;

                    //内部先压入三个函数

                    if (stateString) {

                        list.add(function () {

                            state = stateString;

                            //把互斥的函数和无状态函数都给禁用掉

                        }, tuples[i ^ 1][2].disable, tuples[2][2].lock);

                    }

                    //resolve/reject/notify

                    deferred[tuple[0]] = function () {

                        //这里的this===deferred为什么做这一层判定没有理解

                        //这些触发状态的方法只能是deferred拥有,既然是deferred的触发,那么为何又要阉割当前上下文呢?

                        deferred[tuple[0] "With"](this === deferred ? promise : this, arguments);

                        return this;

                    };

                    //resolveWith/rejectWith/notifyWith

                    deferred[tuple[0] "With"] = list.fireWith;

                });

                //promise有参方法是扩展这个参数

                promise.promise(deferred);

 

                //配合then使用的

                if (func) {

                    func.call(deferred, deferred);

                }

                return deferred;

            }

        });

复制代码

对于基础的API实现[ done/fail/progress | resolve/reject/notify],jQuery把这一部分的代码抽离出来,在后面采用循环一次性动态生成的方式实现。

 

复制代码

var tuples = [

                    ["resolve", "done", jQuery.Callbacks("once memory"), "resolved"],

                    ["reject", "fail", jQuery.Callbacks("once memory"), "rejected"],

                    ["notify", "progress", jQuery.Callbacks("memory")]

                ]

复制代码

首先实现的promise,前面说了,promise是切掉小丁丁版本的Deferred,所以先实现promise,后面把它的API扩展到Deferred里面即可。

 

复制代码

promise = {

                    state: function () {},

                    always: function () {},

                    then: function ( /* fnDone, fnFail, fnProgress */) { },

                    promise: function (obj) {

                        //有参的它为了这个参数扩展了promise行为

                        return obj != null ? jQuery.extend(obj, promise) : promise;

                    }

                }

复制代码

注意这个promise()方法的实现,无参的它把Promise对象的行为扩展到Deferred,后面就直接用这个方法扩展Deferred就可以让Deferred对象拥有promise的API了。

 

在前面准备工作完毕了之后,生成通用的部分,直接循环上面定义的通用数组,直接把Callbacks对象相应的方法引用到API上,因为我们之前Callbacks内部的实现,最终返回的都是this,这里直接引用过去之后,this就代表了Deferred/Promise对象,仍然支持链式回调。

 

在循环中,这里的代码很是心思慎密:

 

复制代码

jQuery.each(tuples, function (i, tuple) {

                    var list = tuple[2],

                    stateString = tuple[3];

                    promise[tuple[1]] = list.add;

                    //内部先压入三个函数

                    if (stateString) {

                        list.add(function () {

                            state = stateString;

                            //把互斥的函数和无状态函数都给禁用掉,i^1位运算,跑下控制台就知道了

                        }, tuples[i ^ 1][2].disable, tuples[2][2].lock);

                    }

                    //resolve/reject/notify

                    deferred[tuple[0]] = function () {

                        //这里的this===deferred为什么做这一层判定没有理解

                        //这些触发状态的方法只能是deferred拥有,既然是deferred的触发,那么为何又要阉割当前上下文呢?

                        deferred[tuple[0] "With"](this === deferred ? promise : this, arguments);

                        return this;

                    };

                    //resolveWith/rejectWith/notifyWith

                    deferred[tuple[0] "With"] = list.fireWith;

                });

复制代码

stateString取值范围(上面数组的定义中)有三个:"resolved"、"rejected"、undefined。

 

所以进入这个判定之后,变量i的值只可能是0||1。

 

然后给Callbacks中压入三个响应的回调函数,分别执行了修改状态字符串、将互斥的函数设置为不可用、锁定无状态的函数,后两个直接引用了Callbacks的方法。这里:通过位运算符得到互斥的索引,然后根据索引访问上面数组里对应的Callbacks,直接禁用和锁定。

 

也就是说,Deferred默认为每个状态压入了三个函数,当我们使用done/fail/progress的时候,是在这三个函数之后执行的,当首次执行触发状态函数(resolve/reject/notify),先执行了这三个函数,再来美图一张,演示了Deferred整个内部模型:

 

 

 

后面的代码,木有了!!!!你木有看错,是真的木有了!!!有这么一点点啊!真的就这么一点点代码!!!有木有感觉so easy?随手就写了一个Deferred有木有啊?!

 

小伙鸡,还有一个大块头呢,不要忽略这个API——Deferred.then()!

 

jQuery.Deferred的then()

这个then()啊,很是巧妙,读起来就简直就是各种痛经啊。我们先再来详细撸一发then()的定义。

 

then的定义:

Promise和Deferred共同拥有API——then():上面的源码里可以看见,Deferred里面本质上是三个Callbacks在工作,,分别存放着不同状态下都要执行的函数列表,看过别人的解释:如果我们添加一个成功状态下要执行的函数,那么大家可能想着调用Deferred.done()。而then()呢,提供了一个便捷的API,then()接收三个参数,分别表示:成功状态下执行的函数,失败状态下执行的函数,每次触发状态下执行的函数——其实意思上就是把done/fail/progress合并到了一个API。

 

嗯,这是在jQuery.1.8以前then()的实现,在jQuery.1.8以前,then()只是一个普通的实现,1.7.2中它的实现:

 

 then: function (doneCallbacks, failCallbacks, progressCallbacks) {

                deferred.done(doneCallbacks).fail(failCallbacks).progress(progressCallbacks);

                return this;

            }

可以看见只是就是直接调用了Call自己的API啊,真真正正的提供了便捷的API入口。我们还是撸一下then的前世吧,在jQuery.1.8以前,有一个APIDeferred.pipe():这API的作用是:提供一个类似always()的API,也就是三个参数,分别表示done、fail、progress状态的函数,也就是把这个三个API合并到一起了,同时,这些函数都可以沟通。

 

jQuery.1.8以后,Deferred.pipe()过时,取代它的API就是Deferred.then()。

 

什么叫做这些函数可以沟通?看如下代码(Deferred.then):

 

复制代码

//普通的应用

        !(function () {

            var deferred = $.Deferred();

            deferred.done(function (value) {

                return value * 10;

            })

                .done(function (value) {

                    console.log(value);

                });

            deferred.resolve(1); //result ---- 1

        })();

 

        //then的应用

        !(function () {

            var deferred = $.Deferred();

            deferred.then(function (value) {

                return value * 10;

            }).then(function (value) {

                console.log(value);

            });

            deferred.resolve(1); //result ---- 10

        })();

复制代码

通过then()添加的函数,同一状态下,上一个函数的返回值可以传递到下一层,这就是then/pipe的实现。

 

Deferred.then:

 

复制代码

then: function ( /* fnDone, fnFail, fnProgress */) {

                    //保存参数

                    var fns = arguments;

                    //注意这里的Deferred,Deferred参数如果是一个函数,那么会直接执行这个函数,参数就是闭包里的deferred对象!!

                    return jQuery.Deferred(function (newDefer) {

                        jQuery.each(tuples, function (i, tuple) {

                            var fn = jQuery.isFunction(fns[i]) && fns[i];

                            // deferred[ done | fail | progress ] for forwarding actions to newDefer

                            //注意这里已经把then()里面的函数封装到了上一层deferred对象中

                            deferred[tuple[1]](function () {

                                var returned = fn && fn.apply(this, arguments);

                                if (returned && jQuery.isFunction(returned.promise)) {

                                    //这一层判定的判定扩展了函数返回的Promise/Deferred对象,这里应该是给jQuery.when()方法使用的

                                    returned.promise()

                                        .done(newDefer.resolve)

                                        .fail(newDefer.reject)

                                        .progress(newDefer.notify);

                                    //其实这里的扩展,应该只是纯粹的对具有promise/A的扩展,只是留了这个功能,什么时候执行,并不是jQuery.Deferred关心的事情

                                } else {

                                    //这是then方法的本质,使用then()返回的promise对象依赖于newDefer对象

                                    //then方法中,这里把上一层的返回值传递到下一层

                                    //而现在的环境只能被最顶层的Deferred触发

                                    //在触发顶层的Deferred中,触发then()中的Deferred

                                    //这里的判定,为什么要做这一层对象的封装呢?

                                    newDefer[tuple[0] "With"](this === promise ? newDefer.promise() : this, fn ? [returned] : arguments);

                                }

                            });

                        });

                        //这里可以放心释放fns,在上面的each中,已经单独创建了对应了变量

                        fns = null;

                    }).promise();

                }

复制代码

实现上比较饶,做了这些事情:

 

1、创建了一个新的Deferred对象,Deferred对象构造函数里,如果传入一个函数作为参数,那么这个函数就会立即执行,这个函数的参数和上下文,就是新创建的Deferred对象。

2、因为then的API承接done/fail/progress这些函数,所以循环上面定义的那个公共部分的数组,一次循环三个函数一并处理了。

3、在每次循环中,创建一个匿名的函数,添加到上一层的Deferred对象中,通过done/fail/progress添加,所以这个函数,会在上一层Deferred对象标志状态的时候(resolve/reject/notify)被执行,这一步其实是在封装通过then()添加进来的函数。

4、在匿名函数中,执行通过then()添加进来的对应状态的函数,并获取到返回值。

5、做了一次返回值的判定,如果这个返回值拥有promise/A的行为,则把当前Deferred对象里面所有的函数扩展到这个返回值对象中,注意是当前Deferred,而不是闭包外的Deferred,then中当前Deferred和then之外的Deferred是两个对象。

6、如果这个返回值不具有promise/A的行为,则直接执行当前Deferred对象相应标识状态的函数(resolve/reject/notify)

- Deferred Deferred首次出现在jQuery 1.5中,在jQuery 1.8之后被改写,它的出现抹平了javascript中的大量回调产生的金字塔,提供了异步...

Deferred对象简史

Deferred对象是在 jQuery 1.5 中引入的,该对象提供了一系列的方法,可以将多个回调函数注册进一个回调队列里、调用回调队列,以及将同步或异步函数执行结果的成功还是失败传递给对应的处理函数。从那以后,Deferred 对象就成了讨论的话题, 其中不乏批评意见,这些观点也一直在变化。一些典型的批评的观点如《你并没有理解 Promise 》和《论 Javascript 中的 Promise 以及 jQuery 是如何把它搞砸的》。

Promise 对象 是和 Deferred 对象一起作为 jQuery 对 Promise 的一种实现。在 jQuery1.x 和 2.x 版本中, Deferred 对象遵守的是《CommonJS Promises 提案》中的约定,而 ECMAScript 原生 promises 方法的建立基础《Promises/A 提案》也是以这一提案书为根基衍生而来。所以就像我们一开始提到的,之所以 Deferred 对象没有遵循《Promises/A 提案》,是因为那时后者根本还没被构想出来。

由于 jQuery 扮演的先驱者的角色以及后向兼容性问题,jQuery1.x 和 2.x 里 promises 的使用方式和原生 Javascript 的用法并不一致。此外,由于 jQuery 自己在 promises 方面遵循了另外一套提案,这导致它无法兼容其他实现 promises 的库,比如 Q library。

不过即将到来的 jQuery 3 改进了 同原生 promises(在 ECMAScript2015 中实现)的互操作性。虽然为了向后兼容,Deferred 对象的主要方法之一(then())的方法签名仍然会有些不同,但行为方面它已经同 ECMAScript 2015 标准更加一致。

jQuery的所有Ajax操作函数,默认返回的就是一个deferred对象。

jQuery中的回调函数

举一个例子来理解为什么我们需要用到 Deferred对象。使用 jQuery 时,经常会用到它的 ajax 方法执行异步的数据请求操作。我们不妨假设你在开发一个页面,它能够发送 ajax 请求给 GitHub API,目的是读取一个用户的 Repository 列表、定位到最近更新一个 Repository,然后找到第一个名为“README.md”的文件并获取该文件的内容。所以根据以上描述,每一个请求只有在前一步完成后才能开始。换言之,这些请求必须依次执行

上面的描述可以转换成伪代码如下(注意我用的并不是真正的 Github API):

JavaScript

var username = 'testuser'; var fileToSearch = 'README.md'; $.getJSON('' username '/repositories', function(repositories) { var lastUpdatedRepository = repositories[0].name; $.getJSON('' username '/repository/' lastUpdatedRepository '/files', function(files) { var README = null; for (var i = 0; i < files.length; i ) { if (files[i].name.indexOf(fileToSearch) >= 0) { README = files[i].path; break; } } $.getJSON('' username '/repository/' lastUpdatedRepository '/file/' README '/content', function(content) { console.log('The content of the file is: ' content); }); }); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var username = 'testuser';
var fileToSearch = 'README.md';
 
$.getJSON('https://api.github.com/user/' username '/repositories', function(repositories) {
  var lastUpdatedRepository = repositories[0].name;
 
$.getJSON('https://api.github.com/user/' username '/repository/' lastUpdatedRepository '/files', function(files) {
    var README = null;
 
for (var i = 0; i < files.length; i ) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;
 
break;
      }
    }
 
$.getJSON('https://api.github.com/user/' username '/repository/' lastUpdatedRepository '/file/' README '/content', function(content) {
      console.log('The content of the file is: ' content);
    });
  });
});

如你所见,使用回调函数的话,我们需要反复嵌套来让 ajax 请求按照我们希望的顺序执行。当代码里出现许多嵌套的回调函数,或者有很多彼此独立但需要将它们同步的回调时,我们往往把这种情形称作“回调地狱 ( callback hell )“。

为了稍微改善一下,你可以从我创建的匿名函数中提取出命名函数。但这帮助并不大,因为我们还是在回调的地狱中,依旧面对着回调嵌套和同步的难题。这时是 DeferredPromise对象上场的时候了。

Promises是什么

Deferred和Promise对象

Deferred 对象可以被用来执行异步操作,例如 Ajax 请求和动画的实现。在 jQuery 中,Promise对象是只能由Deferred对象或 jQuery 对象创建。它拥有 Deferred 对象的一部分方法:always(),done(), fail(), state()then()。我们在下一节会讲到这些方法和其他细节。

如果你来自于原生 Javascript 的世界,你可能会对这两个对象的存在感到迷惑:为什么 jQuery 有两个对象(DeferredPromise)而原生JS 只有一个(Promise)? 在我著作的书《jQuery 实践(第三版)》里有一个类比,可以用来解释这个问题。

Deferred对象通常用在从异步操作返回结果的函数里(返回结果可能是 error,也可能为空)——即结果的生产者函数里。而返回结果后,你不想让读取结果的函数改变 Deferred 对象的状态(译者注:包括 Resolved 解析态,Rejected 拒绝态),这时就会用到 promise 对象——即 Promise 对象总在异步操作结果的消费者函数里被使用。

为了理清这个概念,我们假设你需要实现一个基于 promise 的timeout()函数(在本文稍后会展示这个例子的代码)。你的函数会等待指定的一段时间后返回(这里没有返回值),即一个生产者函数而这个函数的对应消费者们并不在乎操作的结果是成功(解析态 resolved)还是失败(拒绝态 rejected),而只关心他们需要在 Deferred 对象的操作成功、失败,或者收到进展通知后紧接着执行一些其他函数。此外,你还希望能确保消费者函数不会自行解析或拒绝 Deferred对象。为了达到这一目标,你必须在生产者函数timeout()中创建 Deferred 对象,并只返回它的 Promise 对象,而不是 Deferred对象本身。这样一来,除了timeout()函数之外就没有人能够调用到resolve()reject()进而改变 Deferred 对象的状态了。

在这个 StackOverflow 问题 里你可以了解到更多关于 jQuery 中 Deferred 和 Promise 对象的不同。

既然你已经了解里这两个对象,让我们来看一下它们都包含哪些方法。

由于JavaScript单线程的特点,如果某个操作耗时很长,其他操作就必需排队等待。为了避免整个程序失去响应,通常的解决方法是将那些排在后面的操作,写成“回调函数”(callback)的形式。这样做虽然可以解决问题,但是有一些显著缺点:

Deferred对象的方法

Deferred对象相当灵活并提供了你可能需要的所有方法,你可以通过调用 jQuery.Deferred() 像下面一样创建它:

JavaScript

var deferred = jQuery.Deferred();

1
var deferred = jQuery.Deferred();

或者,使用 $作为 jQuery 的简写:

JavaScript

var deferred = $.Deferred();

1
var deferred = $.Deferred();

创建完 Deferred对象后,就可以使用它的一系列方法。处了已经被废弃的 removed 方法外,它们是:

  • always(callbacks[, callbacks, ..., callbacks]): 添加在该 Deferred 对象被解析或被拒绝时调用的处理函数
  • done(callbacks[, callbacks, ..., callbacks]): 添加在该 Deferred 对象被解析时调用的处理函数
  • fail(callbacks[, callbacks, ..., callbacks]): 添加在该 Deferred 对象被拒绝时调用的处理函数
  • notify([argument, ..., argument]):调用 Deferred 对象上的 progressCallbacks 处理函数并传递制定的参数
  • notifyWith(context[, argument, ..., argument]): 在制定的上下文中调用 progressCallbacks 处理函数并传递制定的参数。
  • progress(callbacks[, callbacks, ..., callbacks]): 添加在该 Deferred 对象产生进展通知时被调用的处理函数。
  • promise([target]): 返回 Deferred 对象的 promise 对象。
  • reject([argument, ..., argument]): 拒绝一个 Deferred 对象并以指定的参数调用所有的failCallbacks处理函数。
  • rejectWith(context[, argument, ..., argument]): 拒绝一个 Deferred 对象并在指定的上下文中以指定参数调用所有的failCallbacks处理函数。
  • resolve([argument, ..., argument]): 解析一个 Deferred 对象并以指定的参数调用所有的 doneCallbackswith 处理函数。
  • resolveWith(context[, argument, ..., argument]): 解析一个 Deferred 对象并在指定的上下文中以指定参数调用所有的doneCallbacks处理函数。
  • state(): 返回当前 Deferred 对象的状态。
  • then(resolvedCallback[, rejectedCallback[, progressCallback]]): 添加在该 Deferred 对象被解析、拒绝或收到进展通知时被调用的处理函数

从以上这写方法的描述中,我想突出强调一下 jQuery 文档和 ECMAScript 标准在术语上的不同。在 ECMAScript 中, 不论一个 promise 被完成 (fulfilled) 还是被拒绝 (rejected),我们都说它被解析 (resolved) 了。然而在 jQuery 的文档中,被解析这个词指的是 ECMAScript 标准中的完成 (fulfilled) 状态。

由于上面列出的方法太多, 这里无法一一详述。不过在下一节会有几个展示 DeferredPromise用法的示例。第一个例子中我们会利用Deferred 对象重写“ jQuery 的回调函数”这一节的代码。第二个例子里我会阐明之前讨论的生产者–消费者这个比喻。

1.回调函数往往写成函数参数的形式,导致函数的输入和输出非常混乱,整个程序的可阅读性差;
2.回调函数往往只能指定一个,如果有多个操作,就需要改写回调函数。
3.整个程序的运行流程被打乱,除错和调试的难度都相应增加。

利用 Deferred 依次执行 Ajax 请求

这一节我会利用Deferred对象和它提供的方法使“jQuery 的回调函数”这一节的代码更具有可读性。但在一头扎进代码之前,让我们先搞清楚一件事:在 Deferred 对象现有的方法中,我们需要的是哪些。

根据我们的需求及上文的方法列表,很明显我们既可以用 done()也可以通过 then()来处理操作成功的情况,考虑到很多人已经习惯了使用JS 的原生 Promise对象,这个示例里我会用 then()方法来实现。要注意 then()done()这两者之间的一个重要区别是 then()能够把接收到的值通过参数传递给后续的 then(),done(),fail()progress()调用。

所以最后我们的代码应该像下面这样:

JavaScript

var username = 'testuser'; var fileToSearch = 'README.md'; $.getJSON('' username '/repositories') .then(function(repositories) { return repositories[0].name; }) .then(function(lastUpdatedRepository) { return $.getJSON('' username '/repository/' lastUpdatedRepository '/files'); }) .then(function(files) { var README = null; for (var i = 0; i < files.length; i ) { if (files[i].name.indexOf(fileToSearch) >= 0) { README = files[i].path; break; } } return README; }) .then(function(README) { return $.getJSON('' username '/repository/' lastUpdatedRepository '/file/' README '/content'); }) .then(function(content) { console.log(content); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var username = 'testuser';
var fileToSearch = 'README.md';
 
$.getJSON('https://api.github.com/user/' username '/repositories')
  .then(function(repositories) {
    return repositories[0].name;
  })
  .then(function(lastUpdatedRepository) {
    return $.getJSON('https://api.github.com/user/' username '/repository/' lastUpdatedRepository '/files');
  })
  .then(function(files) {
    var README = null;
 
for (var i = 0; i < files.length; i ) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;
 
break;
      }
    }
 
return README;
  })
  .then(function(README) {
    return $.getJSON('https://api.github.com/user/' username '/repository/' lastUpdatedRepository '/file/' README '/content');
  })
  .then(function(content) {
    console.log(content);
  });

如你所见,由于我们能够把整个操作拆分成同在一个缩进层级的各个步骤,这段代码的可读性已经显著提高了。

Promises就是为了解决这些问题而提出的,它的主要目的就是取代回调函数,成为非同步操作的解决方案。它的核心思想就是让非同步操作返回一个对象,其他操作都针对这个对象来完成。比如,假定ajax操作返回一个Promise对象。

创建一个基于 Promise 的 setTimeout 函数

你可能已经知道 setTimeout() 函数可以在延迟一个给定的时间后执行某个回调函数,只要你把时间和回调函数作为参数传给它。假设你想要在一秒钟后在控制台打印一条日志信息,你可以用它这样写:

JavaScript

setTimeout( function() { console.log('等待了1秒钟!'); }, 1000 );

1
2
3
4
5
6
setTimeout(
  function() {
    console.log('等待了1秒钟!');
  },
  1000
);

如你所见,setTimeout的第一个参数是要执行的回调函数,第二个参数是以毫秒为单位的等待时间。这个函数数年以来运转良好,但如果现在你需要在 Deferred对象的方法链中引入一段时间的延时该怎么做呢?

下面的代码展示了如何用 jQuery 提供的 Promise对象创建一个基于 promise 的 setTimeout(). 为了达到我们的目的,这里用到了 Deferred对象的 promise()方法。

代码如下:

JavaScript

function timeout(milliseconds) { //创建一个新Deferred var deferred = $.Deferred(); // 在指定的毫秒数之后解析Deferred对象 setTimeout(deferred.resolve, milliseconds); // 返回Deferred对象的Promise对象 return deferred.promise(); } timeout(1000).then(function() { console.log('等待了1秒钟!'); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function timeout(milliseconds) {
  //创建一个新Deferred
  var deferred = $.Deferred();
 
// 在指定的毫秒数之后解析Deferred对象
  setTimeout(deferred.resolve, milliseconds);
 
// 返回Deferred对象的Promise对象
  return deferred.promise();
}
 
timeout(1000).then(function() {
  console.log('等待了1秒钟!');
});

这段代码里定义了一个名为 timeout()的函数,它包裹在 JS 原生的 setTimeout()函数之外。

timeout()里, 创建了一个 Deferred对象来实现在延迟指定的毫秒数之后将 Deferred 对象解析(Resolve)的功能。这里 timeout()函数是值的生产者,因此它负责创建 Deferred对象并返回 Promise对象。这样一来调用者(消费者)就不能再随意解析或拒绝 Deferred 对象。事实上,调用者只能通过 done()fail()这样的方法来增加值返回时要执行的函数。

复制代码 代码如下:

jQuery 1.x/2.x同 jQuery3 的区别

在第一个例子里,我们使用 Deferred对象来查找名字包含“README.md”的文件, 但并没有考虑文件找不到的情况。这种情形可以被看成是操作失败,而当操作失败时,我们可能需要中断调用链的执行并直接跳到程序结尾。很自然地,为了实现这个目的,我们应该在找不到文件时抛出一个异常,并用 fail()函数来捕获它,就像 Javascriopt 的 catch()的用法一样。

在遵守 Promises/A 和 Promises/A 的库里(例如jQuery 3.x),抛出的异常会被转换成一个拒绝操作 (rejection),进而通过 fail()方法添加的失败条件回调函数会被执行,且抛出的异常会作为参数传给这些函数。

在 jQuery 1.x 和 2.x中, 没有被捕获的异常会中断程序的执行。这两个版本允许抛出的异常向上冒泡,一般最终会到达 window.onerror。而如果没有定义异常的处理程序,异常信息就会被显示,同时程序也会停止运行。

为了更好的理解这一行为上的区别,让我们看一下从我书里摘出来的这一段代码:

JavaScript

var deferred = $.Deferred(); deferred .then(function() { throw new Error('一条错误信息'); }) .then( function() { console.log('第一个成功条件函数'); }, function() { console.log('第一个失败条件函数'); } ) .then( function() { console.log('第二个成功条件函数'); }, function() { console.log('第二个失败条件函数'); } ); deferred.resolve();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var deferred = $.Deferred();
deferred
  .then(function() {
    throw new Error('一条错误信息');
  })
  .then(
    function() {
      console.log('第一个成功条件函数');
    },
    function() {
      console.log('第一个失败条件函数');
    }
  )
  .then(
    function() {
      console.log('第二个成功条件函数');
    },
    function() {
      console.log('第二个失败条件函数');
    }
  );
 
deferred.resolve();

jQuery 3.x 中, 这段代码会在控制台输出“第一个失败条件函数” 和 “第二个成功条件函数”。原因就像我前面提到的,抛出异常后的状态会被转换成拒绝操作进而失败条件回调函数一定会被执行。此外,一旦异常被处理(在这个例子里被失败条件回调函数传给了第二个then()),后面的成功条件函数就会被执行(这里是第三个 then()里的成功条件函数)。

在 jQuery 1.x 和 2.x 中,除了第一个函数(抛出错误异常的那个)之外没有其他函数会被执行,所以你只会在控制台里看到“未处理的异常:一条错误信息。”

你可以到下面两个JSBin链接中查看它们的执行结果的不同:

  • jQuery 1.x/2.x
  • jQuery 3

为了更好的改善它同 ECMAScript2015 的兼容性,jQuery3.x 还给 DeferredPromise对象增加了一个叫做 catch()的新方法。它可以用来定义当 Deferred对象被拒绝或 Promise对象处于拒绝态时的处理函数。它的函数签名如下:

JavaScript

deferred.catch(rejectedCallback)

1
deferred.catch(rejectedCallback)

可以看出,这个方法不过是 then(null, rejectedCallback)的一个快捷方式罢了。

var promise = get('');

总结

这篇文章里我介绍了 jQuery 实现的 promises。Promises 让我们能够摆脱那些用来同步异步函数的令人抓狂的技巧,同时避免我们陷入深层次的回调嵌套之中。

除了展示一些示例,我还介绍了 jQuery 3 在同原生 promises 互操作性上所做的改进。尽管我们强调了 jQuery 的老版本同ECMAScript2015 在 Promises 实现上有许多不同,Deferred对象仍然是你工具箱里一件强有力的工具。作为一个职业开发人员,当项目的复杂度增加时,你会发现它总能派上用场。

打赏支持我翻译更多好文章,谢谢!

打赏译者

然后,Promise对象有一个then方法,可以用来指定回调函数。一旦非同步操作完成,就调用指定的回调函数。

打赏支持我翻译更多好文章,谢谢!

任选一种支付方式

图片 1 图片 2

1 赞 5 收藏 评论

复制代码 代码如下:

关于作者:HansDo

图片 3

游走于Web前后端,一直在野路子上摸索着。对美术和数学有心无力(・-・*),尽其所能做一个生产者。 个人主页 · 我的文章 · 18 ·    

图片 4

promise.then(function (content) {
  console.log(content)
})

可以将上面两段代码合并起来,这样程序的流程看得更清楚。

复制代码 代码如下:

get(' (content) {
  console.log(content)
})

在1.7版之前,jQuery的Ajax操作采用回调函数。

复制代码 代码如下:

$.ajax({
    url:"/echo/json/",
    success: function(response)
    {
       console.info(response.name);
    }
});

1.7版之后,Ajax操作直接返回Promise对象,这意味着可以用then方法指定回调函数。

复制代码 代码如下:

$.ajax({
    url: "/echo/json/",
}).then(function (response) {
    console.info(response.name);
});

deferred对象的方法

$.deferred()方法

作用是生成一个deferred对象。

复制代码 代码如下:

var deferred = $.deferred();

done() 和 fail()

这两个方法都用来绑定回调函数。done()指定非同步操作成功后的回调函数,fail()指定失败后的回调函数。

复制代码 代码如下:

var deferred = $.Deferred();
deferred.done(function(value) {
   alert(value);
});

它们返回的是原有的deferred对象,因此可以采用链式写法,在后面再链接别的方法(包括done和fail在内)。

resolve() 和 reject()

这两个方法用来改变deferred对象的状态。resolve()将状态改为非同步操作成功,reject()改为操作失败。

复制代码 代码如下:

var deferred = $.Deferred();
deferred.done(function(value) {
   alert(value);
});
deferred.resolve("hello world");

一旦调用resolve(),就会依次执行done()和then()方法指定的回调函数;一旦调用reject(),就会依次执行fail()和then()方法指定的回调函数。

state方法

该方法用来返回deferred对象目前的状态。

复制代码 代码如下:

var deferred = new $.Deferred();
deferred.state();  // "pending"
deferred.resolve();
deferred.state();  // "resolved"

该方法的返回值有三个:

1.pending:表示操作还没有完成。
2.resolved:表示操作成功。
3.rejected:表示操作失败。

notify() 和 progress()

progress()用来指定一个回调函数,当调用notify()方法时,该回调函数将执行。它的用意是提供一个接口,使得在非同步操作执行过程中,可以执行某些操作,比如定期返回进度条的进度。

复制代码 代码如下:

 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();
        }
    });

then()

then()的作用也是指定回调函数,它可以接受三个参数,也就是三个回调函数。第一个参数是resolve时调用的回调函数,第二个参数是reject时调用的回调函数,第三个参数是progress()方法调用的回调函数。

复制代码 代码如下:

deferred.then( doneFilter [, failFilter ] [, progressFilter ] )

在jQuery 1.8之前,then()只是.done().fail()写法的语法糖,两种写法是等价的。在jQuery 1.8之后,then()返回一个新的deferred对象,而done()返回的是原有的deferred对象。如果then()指定的回调函数有返回值,该返回值会作为参数,传入后面的回调函数。

复制代码 代码如下:

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版本之前,上面代码的结果是:

复制代码 代码如下:

result = 2
result = 2
result = 2

在jQuery 1.8版本之后,返回结果是

复制代码 代码如下:

result = 2
result = 6
result = NaN

这一点需要特别引起注意。

复制代码 代码如下:

$.ajax( url1, { dataType: "json" } )
.then(function( data ) {
    return $.ajax( url2, { data: { user: data.userId } } );
}).done(function( data ) {
  // 从url2获取的数据
});

上面代码最后那个done方法,处理的是从url2获取的数据,而不是从url1获取的数据。

利用then()会修改返回值这个特性,我们可以在调用其他回调函数之前,对前一步操作返回的值进行处理。

复制代码 代码如下:

var post = $.post("/echo/json/")
    .then(function(p){
        return p.firstName;
    });
post.done(function(r){ console.log(r); });

上面代码先使用then()方法,从返回的数据中取出所需要的字段(firstName),所以后面的操作就可以只处理这个字段了。

有时,Ajax操作返回json字符串里面有一个error属性,表示发生错误。这个时候,传统的方法只能是通过done()来判断是否发生错误。通过then()方法,可以让deferred对象调用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");
    });

always()

always()也是指定回调函数,不管是resolve或reject都要调用。

pipe方法

pipe方法接受一个函数作为参数,表示在调用then方法、done方法、fail方法、always方法指定的回调函数之前,先运行pipe方法指定的回调函数。它通常用来对服务器返回的数据做初步处理。

promise对象

大多数情况下,我们不想让用户从外部更改deferred对象的状态。这时,你可以在deferred对象的基础上,返回一个针对它的promise对象。我们可以把后者理解成,promise是deferred的只读版,或者更通俗地理解成promise是一个对将要完成的任务的承诺。

你可以通过promise对象,为原始的deferred对象添加回调函数,查询它的状态,但是无法改变它的状态,也就是说promise对象不允许你调用resolve和reject方法。

复制代码 代码如下:

function getPromise(){
    return $.Deferred().promise();
}
try{
    getPromise().resolve("a");
} catch(err) {
    console.log(err);
}

上面的代码会出错,显示TypeError {} 。

jQuery的ajax() 方法返回的就是一个promise对象。此外,Animation类操作也可以使用promise对象。

复制代码 代码如下:

var promise = $('div.alert').fadeIn().promise();

$.when()方法

$.when()接受多个deferred对象作为参数,当它们全部运行成功后,才调用resolved状态的回调函数,但只要其中有一个失败,就调用rejected状态的回调函数。它相当于将多个非同步操作,合并成一个。

复制代码 代码如下:

$.when(
    $.ajax( "/main.php" ),
    $.ajax( "/modules.php" ),
    $.ajax( "/lists.php" )
).then(successFunc, failureFunc);

上面代码表示,要等到三个ajax操作都结束以后,才执行then方法指定的回调函数。

when方法里面要执行多少个操作,回调函数就有多少个参数,对应前面每一个操作的返回结果。

复制代码 代码如下:

$.when(
    $.ajax( "/main.php" ),
    $.ajax( "/modules.php" ),
    $.ajax( "/lists.php" )
).then(function (resp1, resp2, resp3){
    console.log(resp1);
    console.log(resp2);
    console.log(resp3);
});

上面代码的回调函数有三个参数,resp1、resp2和resp3,依次对应前面三个ajax操作的返回结果。

when方法的另一个作用是,如果它的参数返回的不是一个Deferred或Promise对象,那么when方法的回调函数将 立即运行。

复制代码 代码如下:

$.when({testing: 123}).done(function (x){
  console.log(x.testing); // "123"
});

上面代码中指定的回调函数,将在when方法后面立即运行。

利用这个特点,我们可以写一个具有缓存效果的异步操作函数。也就是说,第一次调用这个函数的时候,将执行异步操作,后面再调用这个函数,将会返回缓存的结果。

复制代码 代码如下:

function maybeAsync( num ) {
  var dfd = $.Deferred();
  if ( num === 1 ) {
    setTimeout(function() {
      dfd.resolve( num );
    }, 100);
    return dfd.promise();
  }
  return num;
}
$.when(maybeAsync(1)).then(function (resp){
  $('#target').append('<p>' resp '</p>');
});
$.when(maybeAsync(0)).then(function (resp){
  $('#target').append( '<p>' resp '</p>');
});

上面代码表示,如果maybeAsync函数的参数为1,则执行异步操作,否则立即返回缓存的结果。

实例

wait方法

我们可以用deferred对象写一个wait方法,表示等待多少毫秒后再执行。

复制代码 代码如下:

$.wait = function(time) {
  return $.Deferred(function(dfd) {
    setTimeout(dfd.resolve, time);
  });
}

使用方法如下:

复制代码 代码如下:

$.wait(5000).then(function() {
  alert("Hello from the future!");
});

改写setTimeout方法

在上面的wait方法的基础上,还可以改写setTimeout方法,让其返回一个deferred对象。

复制代码 代码如下:

function doSomethingLater(fn, time) {
  var dfd = $.Deferred();
  setTimeout(function() {
    dfd.resolve(fn());
  }, time || 0);
  return dfd.promise();
}
var promise = doSomethingLater(function (){
  console.log( '已经延迟执行' );
}, 100);

自定义操作使用deferred接口

我们可以利用deferred接口,使得任意操作都可以用done()和fail()指定回调函数。

复制代码 代码如下:

Twitter = {
  search:function(query) {
    var dfr = $.Deferred();
    $.ajax({
     url:"",
     data:{q:query},
     dataType:'jsonp',
     success:dfr.resolve
    });
    return dfr.promise();
  }
}

使用方法如下:

复制代码 代码如下:

Twitter.search('intridea').then(function(data) {
  alert(data.results[0].text);
});

deferred对象的另一个优势是可以附加多个回调函数。

复制代码 代码如下:

function doSomething(arg) {
  var dfr = $.Deferred();
  setTimeout(function() {
    dfr.reject("Sorry, something went wrong.");
  });
  return dfr;
}
doSomething("uh oh").done(function() {
  alert("Won't happen, we're erroring here!");
}).fail(function(message) {
  alert(message)
});

您可能感兴趣的文章:

  • jQuery的deferred对象使用详解
  • jQuery Deferred和Promise创建响应式应用程序详细介绍
  • 利用jQuery的deferred对象实现异步按顺序加载JS文件
  • jQuery源码分析-05异步队列 Deferred 使用介绍
  • jQuery $.extend()用法总结
  • jQuery插件开发的两种方法及$.fn.extend的详解
  • jQuery.extend 函数详解
  • 原生js实现复制对象、扩展对象 类似jquery中的extend()方法
  • jQuery.extend()的实现方式详解及实例
  • jQuery中的deferred对象和extend方法详解

本文由星彩网app下载发布于前端技术,转载请注明出处:jQuery的Deferred对象概述,jQuery之Deferred对象详明

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。