jQuery的deferred对象详解

作者: 阮一峰

日期: 2011年8月16日

jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本。

每个版本都会引入一些新功能。今天我想介绍的,就是从jQuery 1.5.0版本开始引入的一个新功能----deferred对象

这个功能很重要,未来将成为jQuery的核心方法,它彻底改变了如何在jQuery中使用ajax。为了实现它,jQuery的全部ajax代码都被改写了。但是,它比较抽象,初学者很难掌握,网上的教程也不多。所以,我把自己的学习笔记整理出来了,希望对大家有用。

本文不是初级教程,针对的读者是那些已经具备jQuery使用经验的开发者。如果你想了解jQuery的基本用法,请阅读我编写的《jQuery设计思想》《jQuery最佳实践》

======================================

jQuery的deferred对象详解

作者:阮一峰

一、什么是deferred对象?

开发网站的过程中,我们经常遇到某些耗时很长的javascript操作。其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们都不是立即能得到结果的。

通常的做法是,为它们指定回调函数(callback)。即事先规定,一旦它们运行结束,应该调用哪些函数。

但是,在回调函数方面,jQuery的功能非常弱。为了改变这一点,jQuery开发团队就设计了deferred对象

简单说,deferred对象就是jQuery的回调函数解决方案。在英语中,defer的意思是"延迟",所以deferred对象的含义就是"延迟"到未来某个点再执行。

它解决了如何处理耗时操作的问题,对那些操作提供了更好的控制,以及统一的编程接口。它的主要功能,可以归结为四点。下面我们通过示例代码,一步步来学习。

二、ajax操作的链式写法

首先,回顾一下jQuery的ajax操作的传统写法:

  $.ajax({

    url: "test.html",

    success: function(){
      alert("哈哈,成功了!");
    },

    error:function(){
      alert("出错啦!");
    }

  });

(运行代码示例1

在上面的代码中,$.ajax()接受一个对象参数,这个对象包含两个方法:success方法指定操作成功后的回调函数,error方法指定操作失败后的回调函数。

$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5.0版本,返回的是deferred对象,可以进行链式操作。

现在,新的写法是这样的:

  $.ajax("test.html")

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例2

可以看到,done()相当于success方法,fail()相当于error方法。采用链式写法以后,代码的可读性大大提高。

三、指定同一操作的多个回调函数

deferred对象的一大好处,就是它允许你自由添加多个回调函数。

还是以上面的代码为例,如果ajax操作成功后,除了原来的回调函数,我还想再运行一个回调函数,怎么办?

很简单,直接把它加在后面就行了。

  $.ajax("test.html")

  .done(function(){ alert("哈哈,成功了!");} )

  .fail(function(){ alert("出错啦!"); } )

  .done(function(){ alert("第二个回调函数!");} );

(运行代码示例3

回调函数可以添加任意多个,它们按照添加顺序执行。

四、为多个操作指定回调函数

deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数,这是传统写法做不到的。

请看下面的代码,它用到了一个新的方法$.when()

  $.when($.ajax("test1.html"), $.ajax("test2.html"))

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例4

这段代码的意思是,先执行两个操作$.ajax("test1.html")和$.ajax("test2.html"),如果都成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。

五、普通操作的回调函数接口(上)

deferred对象的最大优点,就是它把这一套回调函数接口,从ajax操作扩展到了所有操作。也就是说,任何一个操作----不管是ajax操作还是本地操作,也不管是异步操作还是同步操作----都可以使用deferred对象的各种方法,指定回调函数。

我们来看一个具体的例子。假定有一个很耗时的操作wait:

  var wait = function(){

    var tasks = function(){

      alert("执行完毕!");

    };

    setTimeout(tasks,5000);

  };

我们为它指定回调函数,应该怎么做呢?

很自然的,你会想到,可以使用$.when():

  $.when(wait())

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例5

但是,这样写的话,done()方法会立即执行,起不到回调函数的作用。原因在于$.when()的参数只能是deferred对象,所以必须对wait()进行改写:

  var dtd = $.Deferred(); // 新建一个deferred对象

  var wait = function(dtd){

    var tasks = function(){

      alert("执行完毕!");

      dtd.resolve(); // 改变deferred对象的执行状态

    };

    setTimeout(tasks,5000);

    return dtd;

  };

现在,wait()函数返回的是deferred对象,这就可以加上链式操作了。

  $.when(wait(dtd))

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例6

wait()函数运行完,就会自动运行done()方法指定的回调函数。

六、deferred.resolve()方法和deferred.reject()方法

如果仔细看,你会发现在上面的wait()函数中,还有一个地方我没讲解。那就是dtd.resolve()的作用是什么?

要说清楚这个问题,就要引入一个新概念"执行状态"。jQuery规定,deferred对象有三种执行状态----未完成,已完成和已失败。如果执行状态是"已完成"(resolved),deferred对象立刻调用done()方法指定的回调函数;如果执行状态是"已失败",调用fail()方法指定的回调函数;如果执行状态是"未完成",则继续等待,或者调用progress()方法指定的回调函数(jQuery1.7版本添加)。

前面部分的ajax操作时,deferred对象会根据返回结果,自动改变自身的执行状态;但是,在wait()函数中,这个执行状态必须由程序员手动指定。dtd.resolve()的意思是,将dtd对象的执行状态从"未完成"改为"已完成",从而触发done()方法。

类似的,还存在一个deferred.reject()方法,作用是将dtd对象的执行状态从"未完成"改为"已失败",从而触发fail()方法。

  var dtd = $.Deferred(); // 新建一个Deferred对象

  var wait = function(dtd){

    var tasks = function(){

      alert("执行完毕!");

      dtd.reject(); // 改变Deferred对象的执行状态

    };

    setTimeout(tasks,5000);

    return dtd;

  };

  $.when(wait(dtd))

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例7

七、deferred.promise()方法

上面这种写法,还是有问题。那就是dtd是一个全局对象,所以它的执行状态可以从外部改变。

请看下面的代码:

  var dtd = $.Deferred(); // 新建一个Deferred对象

  var wait = function(dtd){

    var tasks = function(){

      alert("执行完毕!");

      dtd.resolve(); // 改变Deferred对象的执行状态

    };

    setTimeout(tasks,5000);

    return dtd;

  };

  $.when(wait(dtd))

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

  dtd.resolve();

(运行代码示例8

我在代码的尾部加了一行dtd.resolve(),这就改变了dtd对象的执行状态,因此导致done()方法立刻执行,跳出"哈哈,成功了!"的提示框,等5秒之后再跳出"执行完毕!"的提示框。

为了避免这种情况,jQuery提供了deferred.promise()方法。它的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。

请看下面的代码:

  var dtd = $.Deferred(); // 新建一个Deferred对象

  var wait = function(dtd){

    var tasks = function(){

      alert("执行完毕!");

      dtd.resolve(); // 改变Deferred对象的执行状态

    };

    setTimeout(tasks,5000);

    return dtd.promise(); // 返回promise对象

  };

  var d = wait(dtd); // 新建一个d对象,改为对这个对象进行操作

  $.when(d)

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

  d.resolve(); // 此时,这个语句是无效的

(运行代码示例9

在上面的这段代码中,wait()函数返回的是promise对象。然后,我们把回调函数绑定在这个对象上面,而不是原来的deferred对象上面。这样的好处是,无法改变这个对象的执行状态,要想改变执行状态,只能操作原来的deferred对象。

不过,更好的写法是allenm所指出的,将dtd对象变成wait()函数的内部对象。

  var wait = function(dtd){

    var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象

    var tasks = function(){

      alert("执行完毕!");

      dtd.resolve(); // 改变Deferred对象的执行状态

    };

    setTimeout(tasks,5000);

    return dtd.promise(); // 返回promise对象

  };

  $.when(wait())

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例10

八、普通操作的回调函数接口(中)

另一种防止执行状态被外部改变的方法,是使用deferred对象的建构函数$.Deferred()。

这时,wait函数还是保持不变,我们直接把它传入$.Deferred():

  $.Deferred(wait)

  .done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

(运行代码示例11

jQuery规定,$.Deferred()可以接受一个函数名(注意,是函数名)作为参数,$.Deferred()所生成的deferred对象将作为这个函数的默认参数。

九、普通操作的回调函数接口(下)

除了上面两种方法以外,我们还可以直接在wait对象上部署deferred接口。

  var dtd = $.Deferred(); // 生成Deferred对象

  var wait = function(dtd){

    var tasks = function(){

      alert("执行完毕!");

      dtd.resolve(); // 改变Deferred对象的执行状态

    };

    setTimeout(tasks,5000);

  };

  dtd.promise(wait);

  wait.done(function(){ alert("哈哈,成功了!"); })

  .fail(function(){ alert("出错啦!"); });

  wait(dtd);

(运行代码示例12

这里的关键是dtd.promise(wait)这一行,它的作用就是在wait对象上部署Deferred接口。正是因为有了这一行,后面才能直接在wait上面调用done()和fail()。

十、小结:deferred对象的方法

前面已经讲到了deferred对象的多种方法,下面做一个总结:

  (1) $.Deferred() 生成一个deferred对象。

  (2) deferred.done() 指定操作成功时的回调函数

  (3) deferred.fail() 指定操作失败时的回调函数

  (4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。

  (5) deferred.resolve() 手动改变deferred对象的运行状态为"已完成",从而立即触发done()方法。

  (6)deferred.reject() 这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为"已失败",从而立即触发fail()方法。

  (7) $.when() 为多个操作指定回调函数。

除了这些方法以外,deferred对象还有二个重要方法,上面的教程中没有涉及到。

  (8)deferred.then()

有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。

  $.when($.ajax( "/main.php" ))

  .then(successFunc, failureFunc );

如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调方法。如果then()只有一个参数,那么等同于done()。

  (9)deferred.always()

这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行。

  $.ajax( "test.html" )

  .always( function() { alert("已执行!");} );

(致谢:本文第一稿发表后,allenm来信指出原文对promise()的理解是错的。现在的第二稿是根据他的文章修改的,在此我表示衷心感谢。)

(完)

留言(128条)

请问博主,第六条中的 wait函数还是保持不变,是保持第一个版本不变还是保持添加了dtd参数的版本不变?

引用qq的发言:

请问博主,第六条中的 wait函数还是保持不变,是保持第一个版本不变还是保持添加了dtd参数的版本不变?

我开始对这里也疑问,不过可以看 代码示例6。

阮兄是做什么工作的?每天都有时间研究这些,是在政府机关吧?

真是变化好大啊。新的方式写的代码很fluent...

dojo早就有了。

刚看API呢。学习啦。

node.js 里有与jquery的deferred对象等价的对象吗?

博主您好,
我看了这篇文章有几点感悟不知道是不是对的.1、用deferred可以简化$.ajax,但是deferred也没有丰满$.ajax的功能.因为$.ajax同样有seccess,error的方法,而且方法里面 你还能调用其他方法.success:myfunction 这样也能复用.而且$.ajax也有异步或同步的属性.2、deferred的出现解决了一个非ajax请求的监听事件是否执行成功的一系列方法。
不知道我的看法是不是对的。还请博主指教。

@白洁:

我的理解是,deferred主要解决回调函数的部署,它与$.ajax方法的设计各有侧重。

趋势是$.ajax将来会完全使用deferred,作为自己的回调函数接口。有文章称,success方法将在1.8.0版本被取消。

你的博客访问速度好强大啊,太快了

我怎么觉得还是原来的做得顺手~~

引用seasunk的发言:

我怎么觉得还是原来的做得顺手~~

我觉得原来的ajax方法更容易让初学者使用,现在这个有点摸不着头脑

引用贝壳里的海的发言:

你的博客访问速度好强大啊,太快了

阮兄弟的主机在香港。

请教楼主,有什么方法能让在then里面产生的数据传递到done中,比如:

$.when(function(){
}).then(function(){
var arg = '';
}).done(function(){
//如何得到上面arg参数?
});

引用gwallan的发言:

有什么方法能让在then里面产生的数据传递到done中,比如:

把数据绑在this关键字上面。

Jquery简直就像封装了一个语言层面的异步语法,创建并就绪,执行,或成功或失败,最后再加个处理.如果能再加上一个主动超时处理就更强了.

dtd.promise()的目的,就是保证目前的执行状态----也就是"未完成"----不变,从而确保只有操作完成后,才会触发回调函数。

这个还是删掉吧,文章作者根本没理解什么是promise只是从字面上去推定这个函数的意义

引用自由过度的发言:

dtd.promise()的目的,就是保证目前的执行状态----也就是"未完成"----不变,从而确保只有操作完成后,才会触发回调函数。

这个还是删掉吧,文章作者根本没理解什么是promise只是从字面上去推定这个函数的意义

其实我们可以看到,var d = $.Deferred(),与 var p = d.promise(); 这两者的区别主要就在于, d 包含了 resolve, resolveWith, reject 与 rejectWith 这四个方法。而这四个方法就是用来触发 done, fail, always 这些个回调函数的。
之所以要返回 d.promise(): 一是因为 CommonJS promise/A 本来就应当是这样子的;二也是用来避免返回的对象能够主动地调用到resolve与reject这些关键性的方法。

引用白洁的发言:
博主您好,我看了这篇文章有几点感悟不知道是不是对的.1、用deferred可以简化$.ajax,但是deferred也没有丰满$.ajax的功能.因为$.ajax同样有seccess,error的方法,而且方法里面 你还能调用其他方法.success:myfunction 这样也能复用.而且$.ajax也有异步或同步的属性.2、deferred的出现解决了一个非ajax请求的监听事件是否执行成功的一系列方法。不知道我的看法是不是对的。还请博主指教。

不影响原来ajax的使用,异步或同步的属性,还有传递的参数的使用方式还是和原来的一样。

受益匪浅,比官方的解释好

今天看到1.7出来,就找教程看看,阮兄弟的文章不错.顶一个,主机放HK就是好,我的移到ix,尼玛.百度清站了,据说是不能正常打开..坑爹..

引用hileon的发言:

node.js 里有与jquery的deferred对象等价的对象吗?

... node是js引擎,jQuery是支持的.

引用ifree的发言:

... node是js引擎,jQuery是支持的.

node是“宿主环境”(我理解为JVM子类的),它是由V8引擎来执行JS,并实现了CommonJS标准库,赋予JS更多强大的能力。

看得晕乎乎的

dtd,虽然是个局部变量,但还是建议改一下吧,应该是:dfd 吧。dtd在这里莫名其妙了。

受益匪浅!

但是第八点有误
$.Deferred(wait)
这里的wait和上面不一样,在wait再定义一个dtd将不能正常工作。

引用阮一峰的发言:

趋势是$.ajax将来会完全使用deferred,作为自己的回调函数接口。有文章称,success方法将在1.8.0版本被取消。

如果success被取消了,
success:function(rel){//处理rel}
那之前的ajax返回的数据怎么传递给done()?

很受益,谢谢

相当不错的文章,感谢

我觉得Deferred这个功能,并不能真正解决对本地非异步方法的封装,因为javascript是单线程的,如果该方法需要很长时间执行,例如下面这样一个函数

var array = new Array();
var i = 1;
while (i array.push(i);
array = array.reverse();
i++;
}


那么,在执行这个函数的时候,整个页面还是被卡住的。即便可以通过本文所说的技术用Deffered。

有没有什么方法将这种长时间的方法,真正实现出来异步的效果,做完之后再调用回调函数即可。

请阮兄指教

致谢为后来者提供有用知识的先驱!

第七节的最后一个示例代码

"var wait = function(dtd){"
应为:
"var wait = function(){"

使用deferred对象写JS动画非常方便,例如:

// Animation flows.
$.when( preloadImage() )
.then( animation01 )
.then( animation02 )
.then( animation03 )
.then( transition )
.then( merge )
.then( zoom )
.then( showContent )
.then( flicker );

优点:
1、可以任意调整动画的先后顺序。
2、添加 SKIP(跳过动画)功能也很方便。
3、调试动画也可以节省大量时间。 把不需要调试的动画项注释掉。 例如:

$.when( preloadImage() )
// .then( animation01 )
// .then( animation02 )
// .then( animation03 )
// .then( transition )
// .then( merge )
// .then( zoom )
.then( showContent )
.then( flicker );

其实我一直觉得那个 dtd 的变量有歧义,还是叫 dfd 比较合适。

很感谢,一看就懂,现学现用

$.when(f1).then(f2)
保证了f2作为f1的回调函数,在f1之后执行?
deffered对象果然只是回调函数解决方案,f1阻塞的时候照样锁死浏览器

看完之后豁然开朗,同时,网站的设计很好看!

希望不久自己能把相关源码分析一下,应用到自己的项目中。

呵呵,文章写的很清晰,从使用上都理解了,关于具体实现的细节我没有看jquery的源码,下面是我自己整理的一个简单关于Defered和when的实现,没有添加参数传递的部分,希望能给大家一点帮助:

地址 http://jsfiddle.net/springwang/uW6tb/9/

最后一例有些问题,可以直接在外部调用 dtd.resolve()。promise方法应该是仅仅扩展指定对象操作Deferred对象的部分API(主要是 done、fail、always等等,没有resolve、reject这类API),这样多个对象可以操作同一个Deferred对象。所以最后那个例子的实现还是需要跟allenm推荐的一样,必须使用函数封装。

引用gwallan的发言:

有什么方法能让在then里面产生的数据传递到done中

 引用阮一峰的发言:

把数据绑在this关键字上面。

在jQuery源码的ajax部分,你可以看到这样的代码
// Success/Error
if ( isSuccess ) {
  deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
} else {
  deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
}

// Complete
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

学习啦,太感谢啦...

如果有三个事件event01,event02,event03,要event01->event02->event03顺序执行。要怎么写?
我试了$.when(event01()).done(event02).done(event03);但是不行,event02和event03同时执行了。

http://jsfiddle.net/McGradi/PUb3Z/

@阿良:

then里头的function直接return就好啦

一直都在关注您的博客 跟您学习了很多
最近在看jQuery源码 翻出来您这篇博客
发现代码示例10里面
"var wait = function(dtd){"
应该是
"var wait = function(){"
希望能跟您学习的更多啊

感觉那个d = wait(dtd);改为
dtd = wait(dtd)更好。
本意是为了防止dtd.resolve()在下面被使用

Very Good !

我喜欢回调的方式,不喜欢链式

谢谢博主! 读一篇博文, 比看两天手册还更清楚

很喜欢这Blog简洁明了的风格!

阮老师的文章,真心不错,一看就懂,顶一个

引用白洁的发言:

博主您好,
我看了这篇文章有几点感悟不知道是不是对的.1、用deferred可以简化$.ajax,但是deferred也没有丰满$.ajax的功能.因为$.ajax同样有seccess,error的方法,而且方法里面 你还能调用其他方法.success:myfunction 这样也能复用.而且$.ajax也有异步或同步的属性.2、deferred的出现解决了一个非ajax请求的监听事件是否执行成功的一系列方法。
不知道我的看法是不是对的。还请博主指教。


ajax用的多了,体会到defferd对象一个非常大的好处:可以很方便地动态设置回调。
比如我写了一个ajax,但是并不知道回调中会执行哪些操作,回调的回调中又会执行哪些操作。
用defferd的话,我可以什么都不写,等以后要用的时候把回调挨个往后边加就好了。 这比自己去预留一些回调的处理方便多了

刚刚想了解deferred和promise就看到了你的大作!比网上其他的文章介绍的好多了~!
十分感谢!

有例子可以运行,不懂得运行就可以理解了。非常感谢

jQuery规定,$.Deferred()可以接受一个函数名(注意,是函数名)作为参数

这个“函数名”怎么理解。。?

博主,有一个问题请教,假设有4个function,A,B,C,D,每个function都有异步调用的操作,我现在需要按顺序执行这4个function,而且要求只有前一个function执行完毕之后,下一个function才能开始。请问用 Deferred 这个函数能完成这样的功能么,如果能,还望不吝赐教

引用kai的发言:

博主,有一个问题请教,假设有4个function,A,B,C,D,每个function都有异步调用的操作,我现在需要按顺序执行这4个function,而且要求只有前一个function执行完毕之后,下一个function才能开始。请问用 Deferred 这个函数能完成这样的功能么,如果能,还望不吝赐教

A.then(B).then(C).then(D)

引用lee的发言:

A.then(B).then(C).then(D)

这样恐怕不行,A,B,C,D是四个异步函数,你可以自己试试

不过,更好的写法是allenm所指出的,将dtd对象变成wait()函数的内部对象。

  var wait = function(dtd){

这句代码function里面的dtd是多余的吧?

引用kai的发言:

这样恐怕不行,A,B,C,D是四个异步函数,你可以自己试试

http://jsfiddle.net/yCv8S/

是dfd不是dtd吧? 0。0

var wait = function (dfd) {
var task = function () {
alert('completed!!');
dfd.resolve();
}
setTimeout(task, 5000);
}
$.Deferred(wait).done(function () {
alert('done')
});
个人觉得这种方式比较简单,也相对好理解;
另外,对于$.Deferred().promise();最后那句总结很明确了---> 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口

赞个

你好,我现在遇到一个问题,就是怎么在执行ajax时触发 defferd.progress的回调函数?
收到留言恳请回复我的邮箱。

不错,相当精辟,深入浅出。

 var dtd = $.Deferred(); // 新建一个deferred对象
  var wait = function(dtd){
    var tasks = function(){
      alert("执行完毕!");
      dtd.resolve(); // 改变deferred对象的执行状态
    };
    setTimeout(tasks,5000);
    return dtd;
  };

可以写成下面这样吧?

 var dtd = $.Deferred(); // 新建一个deferred对象
  var wait = function(){
    var tasks = function(){
      alert("执行完毕!");
      dtd.resolve(); // 改变deferred对象的执行状态
    };
    setTimeout(tasks,5000);
  };

既然是详解,建议加上如何通过resolve返回原函数的执行结果,即如何像回调函数一样传递执行结果。我搞了一下午才搞清楚。
示例:

 var wait = function(dtd){
    var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象
    var tasks = function(){
      alert("执行完毕!");
      dtd.resolve(new Date()); // 执行完成后,返回当前时间
    };

    setTimeout(tasks,5000);
    return dtd.promise(); // 返回promise对象
  };
  $.when(wait())
  .done(function(time){ alert("完成时间:" + time); })
  .fail(function(){ alert("出错啦!"); });

$.Deferred(wait),这样写的话,如果我想往wait里面传递参数的话,除了换成用$.when实现的话,有什么办法?谢谢

跟着跑了一遍,赶脚都能懂,只是不太明白when这些函数的原理

代码示例11里面的dtd变量似乎有些让人迷惑,改成this是不是要好一些?
如:
var wait = function(dtd){

var that = this;
    var tasks = function(){

      console.log("执行完毕!");
that.resolve(); // 改变Deferred对象的执行状态

    };

    setTimeout(tasks,5000);

    return that.promise();

  };
//...

$.when()的参数只能是deferred对象

这个表述有问题,jQuery 官方文档:

If a single argument is passed to jQuery.when() and it is not a Deferred or a Promise, it will be treated as a resolved Deferred and any doneCallbacks attached will be executed immediately.

实例8是怎么个执行顺序啊,不解

引用小小僧的发言:

如果success被取消了,
success:function(rel){//处理rel}
那之前的ajax返回的数据怎么传递给done()?

.done(function(data))//data 就是返回的数据

请教大侠一个问题:
我是用PHP+JQUERY+AJAX+MYSQL构建的网站,在一个主页面中加载了两个AJAX,也就是引入了两个.JS文件
,在具体执行时总是排在后面的$.AJAX()请求被执行,前一个不执行;也就是说这两个.JS文件谁放在后面谁执行。怎样处理让这两个请求分别执行?

正在学习,很受用

写得很清楚

能不能来点实际的案例,这样才能真正的运用起deferred对象--这个API!不然说了这么多也没有起到作用,真正会用的人不会看,不会用的人看了也不会实际运用!

怎么想后台来传送数据啊?

deferred感觉不符合标准,还是原生的Promise比较好。chrome 42已经内置Promise.http://mustrank.com/blog/Promise.html

Promise当reject时, 没有任务程序去处理, 不是就会被忽略了, 不能像throw那样, 最后报错.
这个问题是, 我在使用Promise时, 发现如果我没有调用catch(), 也不报错, 经常因为没有catch(), 没发现错误.
有什么办法可解决, 当没有catch时, 就抛出错误?

hi,您好, 第七种的最后一个例子 改为了内部局部变量,为什么还要返回dtd.promise(),直接返回dtd,外边的不是也没有办法操作dtd了吗,求解?多谢

看文档术语太多晕晕的,看这个一下就懂,再看文档全懂了。
果然会讲课的好老师太重要了。

引用jonasli的发言:

hi,您好, 第七种的最后一个例子 改为了内部局部变量,为什么还要返回dtd.promise(),直接返回dtd,外边的不是也没有办法操作dtd了吗,求解?多谢

其实改为了内部局部变量之后,是不需要返回dtd.promise(),直接返回dtd就行,不过为啥要返回dtd?

实例10上面的wait函数应该为形参为空

引用水墨丹青的发言:

其实改为了内部局部变量之后,是不需要返回dtd.promise(),直接返回dtd就行,不过为啥要返回dtd?

应该返回dtd或者promise都可以, 但必须返回,因为when的参数必须是defered对象,否则done会马上触发。

打开http://jsfiddle.net里的示例不是太卡就打不开,阮大师能否把示例放到
www.jsbin.com上?

运行代码9感觉没啥用,还是可以用dfd来从外面触发resolve

引用谢武的发言:

运行代码9感觉没啥用,还是可以用dfd来从外面触发resolve

是可以的漏看了把事件绑定在promise上那啦

直接执行wait函数的情况应该很少吧,大多数情况都是需要传参调用的,那如何传参呢?
大多数函数都需要返回值的,那你这里返回 dtd,其他数据怎么返回呢?

引用whws的发言:

直接执行wait函数的情况应该很少吧,大多数情况都是需要传参调用的,那如何传参呢?
大多数函数都需要返回值的,那你这里返回 dtd,其他数据怎么返回呢?

resolve,reject调用作为参数传递,done,fail接收就行、

引用w的发言:

resolve,reject调用作为参数传递,done,fail接收就行、

你说的这个是接收数据返回值。
我上面还有一个问题呢,如何给 wait 函数传参呢?

刚好工作中用到这个,学习了一下,谢谢分享

感觉 这句话不太对,因为done返回一个promise时不会被处理,并且done的参数始终是defer被resolve时的参数,而then会把当前callback的返回值做为下一个then的参数,并且then里的promise会被等待处理掉(当然可能成功或者失败了)

引用小武的发言:

感觉 这句话不太对,

这句话是:有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。

@陈希章:

你可以将你的操作拆分成小的碎片,利用匿名函数与闭包,每100ms中断一次,用setTimeout方法,这样浏览器就会有足够的渲染时间了。

請教一下,promise 的功能,是怕 deferred 是 global 變數的時候會被外部影響,但如果是直接調用 $.Deferred 是不是就沒這個問題,像這樣寫

let roll = () => {
return $.Deferred((dfd) => {
setTimeout(() => {
dfd.resolve();
}, 2000);
});
};

$.when(roll()).then(() => {
console.log('all done');
});


// case2, alert success1, fail 1, fail 2
var dtd = $.Deferred(); // 新建一个Deferred对象
dtd.resolve('success');
dtd.then(function(msg){
alert( msg + ' 1');
return new $.Deferred().reject('fail');
}).fail(function(msg){
alert( msg + ' 1');
return new $.Deferred().resolve('success');
}).then(function(msg){
alert( msg + ' 2');
return msg;
}).fail(function(msg){
alert( msg + ' 2');
return msg;
})

这一段代码执行结果是 success1, fail 1, fail 2. 能解释一下否?

您好 这个写法很好 但是我有一个疑问
这个写法可以像ajax 那样获得数据吗?
$.ajax({
success:function(json){}
})

.done(function(json){}) //好像没看到相应的例子额

我发现每次都是返回一个defer对象 那数据应该怎么返回呢 求教?

博主,请问最后总结那里第(4)点是否写错了, deferred.promise() 返回的是一个promise对象吧.

function foo() {
return $.Deferred(function (defer) {
...
}).promise();
}
我一直用的是这种写法,这样的好处是原始代码极易修改,只要将原来的方法体用这两句话包裹起来,把里面原本return的部分改成defer.resolve()就可以了

难道这就是传说中的Promise >.>

在【五、普通操作的回调函数接口(上)】中有一句“原因在于$.when()的参数只能是deferred对象,所以必须对wait()进行改写:”jQuery文档是这么说的 If a single argument is passed to jQuery.when() and it is not a Deferred or a Promise, it will be treated as a resolved Deferred and any doneCallbacks attached will be executed immediately.

调理清晰,高质量文章。
不过文中提到的Deferred用于普通函数的场景(典型的大数组的遍历,不是用setTimeout模拟),应该并无多大实际意义吧?javascript的执行队列本来就是在单个线程内完成,这种耗时的计算,执行是同步的,代码顺序写应该比较合理吧,用Deferred的意义在哪里呢?

then 不只是简化那么简单哦,还能做到避免回调地狱哦。

为什么大神的名字 这么熟悉,但是我又不知道从哪里得知,好熟悉

学了一年了,现在才知道,囧

第七点,“在原来的deferred对象上返回另一个deferred对象”后面的那个“deferred对象”是不是应该是“promise对象”?

感谢这么精彩的文章。
作者看一下运行实例代码10的第一行

阮老师,我在使用你的react脚手架的github工程的时候,报错了WebpackOptionsValidationError: Invalid configuration object. Webpack has been in
itialised using a configuration object that does not match the API schema.我是在windows下开发的,所以直接下了zip包,没有使用git clone,然后运行了npm install npm start 就报错了。这个错误我定位了很久,但是实在是技术不到家,没办法排查出来。请问这个到底是什么原因?

示例9那里,我把d.resolve改成dtd.resolve就可以改变执行状态了,所以这个示例不对。

//这样写也完全没有全局的变量
!(function () {
"use strict";
var wait = function () {
var dtd = $.Deferred(); // 新建一个deferred对象
var tasks = function () {
alert("执行完毕!");
dtd.resolve(); // 改变deferred对象的执行状态
};
setTimeout(tasks, 1000);
return dtd;
};
$.when(wait())
.done(function () {
alert("哈哈,成功了!");
})
.fail(function () {
alert("出错啦!");
});
}());

promise的前传

通俗易懂,看你的博客很有收获,刚好用到

謝謝 你的指導文章 感恩

最近在看jquery源码,结合着一起看更明白了些,评论更加精彩。这篇文章从11年到17年,精华还在,感谢博主!

虽然是其他链接进来的,但是还是直接解决了我项目中的问题!
ajax 同步阻塞导致的 UI线程阻塞问题!

上面的方法中,用$.when(d).done().fail(),可以改为直接用d.done().fail()吧。这了用$.when没有意义啊?

(下)中的方法不可行。在最后加上dtd.resolve();依然会有问题的

引用阮一峰的发言:

@白洁:

我的理解是,deferred主要解决回调函数的部署,它与$.ajax方法的设计各有侧重。

趋势是$.ajax将来会完全使用deferred,作为自己的回调函数接口。有文章称,success方法将在1.8.0版本被取消。

事实证明就是虾扯蛋,现在3.x都出来了,也没见取消success

fail是只要其中之一不成功就执行,那请教怎样才能allFail,就是全部失败才执行?

引用您的大名的发言:

事实证明就是虾扯蛋,现在3.x都出来了,也没见取消success

现在还有很多程序员不知道deferred,写ajax就是success,error回调处理呢

写的很好

老师,请问新版的写法$.ajax("test.html")在哪里设置请求类型(get/post)呢?另外如果想带查询参数的话需要写在哪里呢?

方法八不对,返回的是deferred对象而非promise对象,还是可以通过外部resolve更改

厉害,思路清晰!

厉害, 跨越了9年, 从11年到20年

"既有异步的操作(比如ajax读取服务器数据)"
老师你好,如您题所说,ajax我在取后台数据时如果时间很长,怎么保证html不会卡住?

引用EvanPei的发言:

"既有异步的操作(比如ajax读取服务器数据)"
老师你好,如您题所说,ajax我在取后台数据时如果时间很长,怎么保证html不会卡住?

做一个数据加载中状态就行了

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接