资讯专栏INFORMATION COLUMN

jQuery Deferred对象

baoxl / 2171人阅读

摘要:给普通的操作指定回调函数对象的最大优点,就是它把这一套回调函数接口,从操作扩展到了所有操作。方法用于指定对象状态为已失败时的回调函数。执行完毕执行成功执行失败接收一个或多个对象作为参数,为其指定回调函数。

什么是deferred对象

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

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

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

它解决了如何处理耗时操作的问题,对那些操作提供了更好的控制,以及统一的编程接口。它的主要功能,可以归结为四点。

ajax的链式写法

我们来看一个$.ajax请求的例子:

</>复制代码

  1. $.ajax({
  2. url: "test.jspx",
  3. success: function(data) {
  4. // 请求成功了
  5. // TODO
  6. },
  7. error: function(error) {
  8. // 请求失败了
  9. // TODO
  10. }
  11. });

上面的代码再常见不过了,不过下面的写法你也一定看过:

</>复制代码

  1. $.ajax("test.jspx")
  2. .done(function(data) {
  3. // 请求成功了
  4. // TODO
  5. })
  6. .fail(function(error) {
  7. // 请求失败了
  8. // TODO
  9. });
  10. $.ajax({
  11. url: "test.jspx",
  12. type:"POST",
  13. data: data
  14. }).done(function(data) {
  15. // 请求成功了
  16. // TODO
  17. }).fail(function(error) {
  18. // 请求失败了
  19. // TODO
  20. });

其实在1.5.0版本的之前的jquery是不支持链式写法的,只能使用第一种写法,原因是此版本之前的jquery的ajax操作返回的是一个XHR (XMLHttpRequest) 对象,这个对象没有像donefail这样的回调方法。

之后的版本返回的是一个deferred对象用promise方法包装过的对象,可以进行链式操作,使用链式写法后,代码可读性大大提高。

为一个操作指定多个回调函数

如果要在一个ajax请求成功后再执行别的回调函数,该怎么办呢? 直接在加在后面就可以了:

</>复制代码

  1. $.ajax("test.jspx")
  2. .done(function(data) {
  3. // 请求成功了
  4. // TODO
  5. }).fail(function(error) {
  6. // 请求失败了
  7. // TODO
  8. }).done(function(data) {
  9. // 请求成功了
  10. // then TODO
  11. });

这种写法可以支持无数个回调函数,这写回调函数将按照添加顺序依次执行。

为多个操作指定回调函数

如果一个回调函数需要在几个ajax请求都成功后才能执行该怎么办呢?是不是不好控制呢?其实jQuery提供了这样一个方法$.when() 它可以接收任意个deferred对象,只有所有的deferred对象都状态都为成功时才执行done回调,否则执行fail回调。

</>复制代码

  1. $.when($.ajax("test1.html"), $.ajax("test2.html"))
  2. .done(function() {
  3. // 两个操作都请求都成功
  4. // TODO
  5. }).fail(function() {
  6. // 任意一个失败
  7. // TODO
  8. });

上面代码中,只有两个请求都成功后才会执行done回调,只要有一个失败就执行fail回调。

给普通的操作指定回调函数

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

</>复制代码

  1. var wait = function() {
  2. var dtd = $.Deferred(); // 新建一个deferred对象
  3.     
  4. var tasks = function() {      
  5. alert("执行完毕!");      
  6. dtd.resolve(); // 此操作完成后改变deferred对象的执行状态     
  7. };    
  8. setTimeout(tasks, 5000);    
  9. return dtd;  
  10. };
  11. // 绑定回调函数   
  12. $.when(wait())
  13. .done(function() {
  14. alert("执行成功了!");
  15. })
  16. .fail(function() {
  17. alert("出错啦!");
  18. });

上面代码的中的wait方法模拟了一个很耗时的操作,之后给这个操作指定了回调函数donefail。一旦wait执行完毕就会立即调用done这个回调函数。

Deferred对象的常用方法

上面已经对deferred对象有所了解了,下面介绍一下deferred对象的常用方法。

deferred.resolve()

在jQuery的deferred对象中,规定了它有三种状态:

</>复制代码

  1. 未完成 继续等待

  2. 已完成 立即调用done回调

</>复制代码

  1. 已失败 立即调用fail回调

resolve方法的作用就是设置deferred对象状态为已完成,deferred对象立刻调用done()方法指定的回调函数

deferred.reject()

reject方法的作用是设置deferred对象状态为已失败,deferred对象立刻调用fail()方法指定的回调函数

deferred.done()

done方法用于指定deferred对象状态为已完成时的回调函数。

deferred.fail()

done方法用于指定deferred对象状态为已失败时的回调函数。

deferred.then()

then方法接收一到三个参数,分别指定deferred对象状态为已成功、已失败和继续等待的回调函数。

deferred.always()

always方法用于指定deferred对象状态为已成功或已失败时的回调函数。

即无论这个deferred对象是成功还是失败,只要执行完毕都会调用此方法指定的回调。

由于此方法指定的回调函数的参数是不确定的(比如ajax请求成功和失败返回的信息不同,成功时为返回的数据,失败则为错误信息),最好只使用它的行为,而不检查其参数。如果要执行的操作和参数有关,请显示地指定donefail回调。如下所示:

</>复制代码

  1. $.ajax("test1.html")
  2. .always(function() {
  3. // 不管请求是否成功,只要请求完毕就执行
  4. // TODO
  5. console.log("已请求完毕,状态未知");
  6. });
  7. $.ajax("test1.html")
  8. .done(function(data) {
  9. // 请求成功时执行
  10. // TODO
  11. console.log("请求已成功,返回数据为:");
  12. console.log(data);
  13. })
  14. .fail(function(error) {
  15. // 请求失败时执行
  16. // TODO
  17. console.log("请求已失败,错误信息:");
  18. console.log(error.status, error.statusText);
  19. });
deferred.progress()

progress方法用于指定deferred对象状态为等待中的回调函数。但是它仅在deferred对象生成了进度通知时才会被调用。

请看下面例子:

</>复制代码

  1. var wait = function() {
  2. var dtd = $.Deferred(); // 新建一个deferred对象
  3.     
  4. var tasks = function() {      
  5. alert("执行完毕!");      
  6. dtd.resolve(); // 此操作完成后改变deferred对象的执行状态     
  7. };    
  8. setTimeout(tasks, 5000);    
  9. return dtd;  
  10. };
  11. // 绑定回调函数   
  12. $.when(wait())
  13. .done(function() {
  14. alert("执行成功了!");
  15. })
  16. .fail(function() {
  17. alert("出错啦!");
  18. })
  19. .progress(function(){
  20. console.log("正在执行中..."); // 此处不会有任何输出
  21. });

上面虽然指定了progress回调,但是却为没有任何作用的原因是由于在deferred对象没有生成进度通知,所以其不会被调用。

想要progress回调能执行,需要在deferred对象上调用此回调。notify方法的作用就是根据给定的 args参数 调用Deferred对象上进行中的progress回调。

</>复制代码

  1. var wait = function() {
  2. var dtd = $.Deferred(); // 新建一个deferred对象
  3. var i = 1,
  4. timer,
  5. percent; // 记录进度
  6. var tasks = function() {
  7. if (i == 11) {
  8. alert("执行完毕!");
  9. dtd.resolve(); // 此操作完成后改变deferred对象的执行状态
  10. } else {
  11. percent = (i * 500) / 5000 * 100 + "%";
  12. dtd.notify(percent); // 调用progress回调
  13. i++;
  14. setTimeout(tasks, 500);
  15. }
  16. };
  17. setTimeout(tasks, 1000);
  18. return dtd;
  19. };
  20. // 绑定回调函数
  21. $.when(wait())
  22. .done(function() {
  23. alert("执行成功了!");
  24. })
  25. .fail(function() {
  26. alert("出错啦!");
  27. })
  28. .progress(function(data) {
  29. console.log("执行中,已完成", data);
  30. });
  31. // 执行中,已完成 10%
  32. // 执行中,已完成 20%
  33. // 执行中,已完成 30%
  34. // 执行中,已完成 40%
  35. // 执行中,已完成 50%
  36. // 执行中,已完成 60%
  37. // 执行中,已完成 70%
  38. // 执行中,已完成 80%
  39. // 执行中,已完成 90%
  40. // 执行中,已完成 100%
  41. // 之后弹出 执行完毕!和 执行成功了!

这个方法给上传文件或者耗时操作生成进度条提供了一种可能。

</>复制代码

  1. jQuery3.0以上版本对when方法做了大幅调整。向promise/A+靠齐,上面的写法中notify是触发不了when中的progress回调的,需要使用promise来给对象部署deferred接口或使用$.Deferred()传入函数名。

简而言之,3.0以以上版本中,上面代码中progress回调是不会进去的,应使用以下写法:

</>复制代码

  1. 1、promise给一个对象部署Deferred接口:

</>复制代码

  1. var dtd = $.Deferred(); // 新建一个deferred对象
  2. var wait = function(dtd) {
  3. var i = 1,
  4. timer,
  5. percent; // 记录进度
  6. var tasks = function() {
  7. if (i == 11) {
  8. alert("执行完毕!");
  9. dtd.resolve(); // 此操作完成后改变deferred对象的执行状态
  10. } else {
  11. percent = (i * 500) / 5000 * 100 + "%";
  12. dtd.notify(percent); // 调用progress回调
  13. i++;
  14. setTimeout(tasks, 500);
  15. }
  16. };
  17. setTimeout(tasks, 1000);
  18. };
  19. // 在wait对象上部署Deferred接口,此后就可以直接在wait上使用deferred对象promise后的方法了
  20. dtd.promise(wait);
  21. // 在wait对象上使用deferred对象的方法指定回调。
  22. wait.done(function() {
  23. alert("执行成功了!");
  24. })
  25. .fail(function() {
  26. alert("出错啦!");
  27. })
  28. .progress(function(data) {
  29. console.log("执行中,已完成", data);
  30. });
  31. // 执行
  32. wait(dtd);

</>复制代码

  1. 2、使用$.Deferred传入函数名:

</>复制代码

  1. var wait = function(dtd) {
  2. var i = 1,
  3. timer,
  4. percent; // 记录进度
  5. var tasks = function() {
  6. if (i == 11) {
  7. alert("执行完毕!");
  8. dtd.resolve(); // 此操作完成后改变deferred对象的执行状态
  9. } else {
  10. percent = (i * 500) / 5000 * 100 + "%";
  11. dtd.notify(percent); // 调用progress回调
  12. i++;
  13. setTimeout(tasks, 500);
  14. }
  15. };
  16. setTimeout(tasks, 1000);
  17. return dtd;
  18. };
  19. // 绑定回调函数
  20. $.Deferred(wait)
  21. .done(function() {
  22. alert("执行成功了!");
  23. })
  24. .fail(function() {
  25. alert("出错啦!");
  26. })
  27. .progress(function(data) {
  28. console.log("执行中,已完成", data);
  29. });
deferred.promise()

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

</>复制代码

  1.   
  2. var dtd = $.Deferred(); // 新建一个Deferred对象
  3.   
  4. var wait = function(dtd) {    
  5. var tasks = function() {      
  6. alert("执行完毕!");      
  7. dtd.resolve(); // 改变Deferred对象的执行状态     
  8. };    
  9. setTimeout(tasks, 5000);    
  10. return dtd;  
  11. };  
  12. $.when(wait(dtd))  
  13. .done(function() {
  14. alert("执行成功!");
  15. })  
  16. .fail(function() {
  17. alert("出错啦!");
  18. });
  19. dtd.reject(); // 改变状态为失败,将立即触发fail 5s后完成再出发done

如果我们把deferred对象定义在了函数外部,那么我们设置deferred对象的状态就会导致调用对应的回调。上面代码中,最后调用reject方法,会导致立即调用了fail回调,5s之后又弹出执行完毕和执行成功。这将会导致不必要的混乱。使用promise方法就是一种解决方案。(之前写的将var dtd = $.Deferred()放在函数内部,使得外部访问不到也是一种解决方案)。

</>复制代码

  1. var dtd = $.Deferred(); // 新建一个Deferred对象
  2.   
  3. var wait = function(dtd) {    
  4. var tasks = function() {
  5. alert("执行完毕!");      
  6. dtd.resolve(); // 改变Deferred对象的执行状态
  7. };    
  8. setTimeout(tasks, 5000);    
  9. return dtd.promise(); // 返回promise对象
  10.   
  11. };  
  12. var d = wait(dtd); // 新建一个d对象,改为对这个对象进行操作
  13.   
  14. $.when(d)
  15. .done(function() {
  16. alert("哈哈,成功了!");
  17. }).fail(function() {
  18. alert("出错啦!");
  19. });  
  20. d.resolve(); // d.resolve is not a function 经过promise后没有resolve方法了

我们看一下Deferred对象和它promise之后的区别。

promise返回的对象上已经去掉了和改变状态有关的方法。notifynotifyWith是调用progress回调,resolvereject用于设置其状态,带with的方法可指定上下文环境。

</>复制代码

  1. 此方法还可以接收Object类型的参数,deferred.promise()会将事件绑定到该参数上,然后返回该对象,而不是创建一个新的对象。 这个方法可以用于在已经存在的对象上绑定 Promise 行为的情况。示例如下:

</>复制代码

  1. var dtd = $.Deferred(); // 生成Deferred对象
  2.   
  3. var wait = function(dtd) {    
  4. var tasks = function() {      
  5. alert("执行完毕!");      
  6. dtd.resolve(); // 执行完毕后改变Deferred对象的执行状态
  7.     
  8. };    
  9. setTimeout(tasks, 5000);  
  10. };  
  11. // 在wait对象上部署Deferred接口,此后就可以直接在wait上使用deferred对象promise后的方法了
  12. dtd.promise(wait);
  13. // 在wait对象上使用deferred对象的方法指定回调。
  14. wait.done(function() {
  15. alert("哈哈,成功了!");
  16. })  .fail(function() {
  17. alert("出错啦!");
  18. });  
  19. // 执行
  20. wait(dtd);
$.Deferred()

$.Deferred()除了创建一个deferred对象之外,可以接受一个函数名(注意,是函数名)作为参数,$.Deferred()所生成的deferred对象将作为这个函数的默认参数。

</>复制代码

  1. var wait = function(dtd) {    
  2. var tasks = function() {      
  3. alert("执行完毕!"); 
  4. dtd.resolve();         
  5. };    
  6. setTimeout(tasks, 5000);      
  7. };
  8. $.Deferred(wait)
  9. .done(function() {
  10. alert("执行成功!");
  11. })
  12. .fail(function() {
  13. alert("执行失败!");
  14. });
$.when()

接收一个或多个deferred对象作为参数,为其指定回调函数。

见为多个操作指定回调函数

deferred.then的传递作用

之前我们介绍了,then方法接一到三个参数,分别指定deferred对象状态为已成功、已失败和继续等待的回调函数。

除此之外,then还可以传递迟延对象。我们知道,deferred可以链式操作的原因是其返回的仍是deferred对象。then中的回调函数如果没有返回新的deferred对象时,将依然使用最开始的那个deferred对象,如果在其也行返回一个deferred对象时,之后的操作将被转移到在回调函数中return出来的新的deferred对象,从而进行传递。

我们来看一个例子:

有5个js文件,其内容如下:

</>复制代码

  1. // perosn.js
  2. var Person = function(name){
  3. this.name= name;
  4. };
  5. // person.prototype.js
  6. if (Person) {
  7. Person.prototype.showName = function() {
  8. return this.name;
  9. };
  10. } else {
  11. console.error("Person is undefined!");
  12. }
  13. // zs.js
  14. var zs = new Person("张三");
  15. // ls.js
  16. var ls = new Person("李四");
  17. // introduce.js
  18. var introduceEachOther = function() {
  19. console.log("张三:你好,我叫" + zs.showName());
  20. console.log("李四:你好,我叫" + ls.showName());
  21. };

文件内容都非常简单,仅做演示使用,但是其中的依赖关系非常明显,person.prototype.js依赖于perosn.js,zs.js和ls.js依赖于person.js,introduce.js依赖于其他所有的js。

要分步加载,并保证可用,就必须保证在加载当前js时,其依赖的js已经加载完毕。

使用传统的写法,势必会嵌套多层,不仅逻辑不好处理,而且可读性很差。

我们用then演示一下如何来传递递延对象来完成这个操作。

</>复制代码

  1. $.ajax({
  2. url: "person.js",
  3. dataType: "script"
  4. }).then(function() {
  5. console.log(Person); // person.js已经加载成功
  6. console.log("person.js已经加载成功");
  7. return $.ajax({
  8. url: "person.prototype.js",
  9. dataType: "script"
  10. });
  11. }).then(function(data) {
  12. // 这里的data 上一步请求文件的内容 是person.prototype.js而非person.js
  13. console.log(data);
  14. console.log(Person.prototype.showName);
  15. console.log("person.prototype.js已经加载成功");
  16. // person.prototype.js 已经加载
  17. return $.when($.ajax({
  18. url: "zs.js",
  19. dataType: "script"
  20. }), $.ajax({
  21. url: "ls.js",
  22. dataType: "script"
  23. }));
  24. }).then(function(){
  25. // zs.js 和 ls.js 都加载完成
  26. console.log(zs,ls);
  27. console.log("zs.jszs.js已经加载成功");
  28. return $.ajax({
  29. url: "introduce.js",
  30. dataType: "script"
  31. });
  32. },function(){
  33. console.log("zs.js or ls.js failed");
  34. }).then(function(){
  35. // 到此前面的所有资源都是加载成功的
  36. console.log("introduce.js已经加载成功,且其依赖资源都加载成功了,可以使用了");
  37. introduceEachOther();
  38. });

在线地址

以上处理顺序只是举例演示,只要保证person.js最先加载,introduce.js最后加载,person.prototype.js在person.js之后即可。ls.js、zs.js与person.prototype.js的顺序可以调整。

我们每次在then的第一个回调中返回了而一个新的递延对象,之后的操作就是基于你返回的那个递延对象了,第二个then回调中输出的data证明了这一点。(如果没有return出一个新的递延对象的话,依然使用之前的那个。)

参考链接:

本文是照着下面的文章加上自己的学习体会书写的。

阮一峰:jQuery的deferred对象详解

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/82273.html

相关文章

  • jquery 中的 deferred 对象

    摘要:中文文档简单说,对象就是的回调函数解决方案。为了让回调函数的名字统一,便于在中使用。普通操作的回调函数接口对象的最大优点,就是它把这一套回调函数接口,从操作扩展到了所有操作。指定操作成功时的回调函数。 参考链接 jQuery API中文文档 jQuery.Deferred jQuery.when jQuery的deferred对象详解 jQuery deferred 对象的 prom...

    Meathill 评论0 收藏0
  • $.when().done().then()的用法

    摘要:通常的做法是,为它们指定回调函数。指定操作成功时的回调函数指定操作失败时的回调函数没有参数时,返回一个新的对象,该对象的运行状态无法被改变接受参数时,作用为在参数对象上部署接口。 jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本。 每个版本都会引入一些新功能。今天我想介绍的,就是从jQuery 1.5.0版本开始引入的一个新功能----deferred对象。 这个功...

    KoreyLee 评论0 收藏0
  • jQuerydeferred对象详解

    摘要:通常的做法是,为它们指定回调函数。简单说,对象就是的回调函数解决方案。指定操作成功时的回调函数指定操作失败时的回调函数没有参数时,返回一个新的对象,该对象的运行状态无法被改变接受参数时,作用为在参数对象上部署接口。 转自:阮一峰:http://www.ruanyifeng.com/blo... 一、什么是deferred对象?开发网站的过程中,我们经常遇到某些耗时很长的javascri...

    lei___ 评论0 收藏0
  • 通过 ES6 Promise 和 jQuery Deferred 的异同学习 Promise

    摘要:和和都有和,但是略有不同。实际上返回的是一个对象。和添加的回调,添加的回调。所以在调用成功的情况下执行添加的回调,调用失败时执行添加的回调。,产生对象并,产生对象并,然后继续处理,的语法糖,和的差不多但不同。 Deferred 和 Promise ES6 和 jQuery 都有 Deffered 和 Promise,但是略有不同。不过它们的作用可以简单的用两句话来描述 Deffere...

    Yujiaao 评论0 收藏0
  • jQuery源码解析Deferred异步对象

    摘要:回调队列对象,用于构建易于操作的回调函数集合,在操作完成后进行执行。对象对象,用于管理回调函数的多用途列表。如果传入一个延迟对象,则返回该对象的对象,可以继续绑定其余回调,在执行结束状态之后也同时调用其回调函数。 在工作中我们可能会把jQuery选择做自己项目的基础库,因为其提供了简便的DOM选择器以及封装了很多实用的方法,比如$.ajax(),它使得我们不用操作xhr和xdr对象,直...

    Coding01 评论0 收藏0
  • Promise介绍--DeferredjQuery

    摘要:我们称为回调对象,它内部会维护一个数组,我们可以向其中添加若干个回调函数,然后在某一条件下触发执行。第一次之后,再次新的回调函数时,自动执行回调。当前面的回调函数返回时,终止后面的回调继续执行。 最近懒癌发作,说好的系列文章,写了一半,一直懒得写,今天补上一篇。 Deferred 我们在使用promise对象时,总会提到一个与它关系密切的对象——Deferred。其实Deferred没...

    Darkgel 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<