• 详解jQuery.Deferred对象
  • 往事如风 发表于 2016/3/18 12:39:00 | 分类标签: jquery Deferred对象
  • 一、前言

    jQuery1.5之前,如果需要多次Ajax操作,我们一般会使用下面的两种方式:

    1).串行调用Ajax

    $.ajax({ success: function() { 
        $.ajax({ success: function() {
            $.ajax({ //callbacks... 
                    }); 
        }); 
    });   

    这种方式代码可读性差,效率低,晦涩难懂,调试和排错的复杂度大。

    2).并行调用Ajax

    var promises = []; 
    $.ajax({
        success: function() { 
            promises.push('resolved'); 
            check(); 
        } 
    }); 
    $.ajax({ 
        success: function() { 
            promises.push('resolved'); 
            check();
        } 
    }); 
    $.ajax({ 
        success: function() { 
            promises.push('resolved'); 
            check(); 
        } 
    }); 
    var check = function() { //checks for all 3 values in the promises array }

    这种方式对于callbacks函数调用来说已经很不错了,并行取得数据,可读性良好。缺点就是代码冗长,可扩展性差,调试和排错的复杂度高。

    jQuery1.5之后,增加了deferred对象。因此可以用下面这种方式实现和上面同样的需求。

    1)Promise

    var address = $.ajax({}); 
    var tweets = $.ajax({}); 
    var facebook = $.ajax({}); 
    render_side_bar = function(address, tweets, facebook){ 
        //render sidebar 
    }
    render_no_side_bar = function () { }
    $.when(address, tweets, facebook).then(render_side_bar, render_no_side_bar)

    可以看出,代码可读性良好,可扩展性高,并且大大降低了调试和排错的复杂度。

    那么问题来了,promises和deferred对象究竟是个什么玩意呢?

    二、详解

    2.什么是deferred对象?

    deferred对象即延迟对象,它是jQuery 1.5版本引入的一种回调函数的解决方案,代表了将要完成的某种操作,并且提供了一些方法,帮助用户使用。

    deferred对象是对Promises接口的实现。jQuery 1.5版本以及之后所有的Ajax返回的jqXHR对象就是一个deferred对象。

    2.deferred对象的几大好处

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

    deferred对象的好处之一,就是它允许你为一个操作添加多个回调函数,这在传统的ajax中是无法实现的。

    $.ajax("test.html")
      .done(function(){ alert("first success callback!");} )
      .fail(function(){ alert("there is an error!"); } )
      .done(function(){ alert("second success callback!");} );

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

    deferred对象的好处之二,就是它允许你为多个操作指定同一个回调函数,这在传统的ajax中也是无法实现的。

    $.when($.ajax({}), $.ajax({}))
      .done(function(){ alert("success!"); })
      .fail(function(){ alert("error!"); });

    2.3.非Ajax操作的回调函数

    deferred对象的好处之三,就是它不再拘泥于ajax操作,任意的操作(ajax操作or本地操作/异步操作or同步操作)都可以使用deferred对象,指定回调函数。

    一个很典型的耗时操作

    var dfd = $.Deferred(); // create a deferred object
      var wait = function(dtd){
        var tasks = function(){
          alert("over!");
          dtd.resolve(); // change the state of the deferred object from pending to resolved
        };
        setTimeout(tasks,50000);
        return dtd;
      };
    $.when(wait(dtd))
      .done(function(){ alert("success!"); })
      .fail(function(){ alert("error!"); });

    2.4.链式调用

    jQuery中传统的ajax操作是这样的:

    $.ajax({
    url:
    "",    success: function(){     alert("success!");    },    error:function(){     alert("error!");    } });

    其中success指定ajax操作成功后的回调函数,error指定ajax操作失败后的回调函数。jQuery1.5版本之前,Ajax操作返回的是一个XMLHTTPRequest对象,不支持链式操作。1.5版本开始,ajax操作返回的是jqXHR对象,这是一个deferred对象,而deferred对象一个显著的好处就是可以进行链式操作,因为deferred对象的所有方法返回的均是deferred对象。

    现在的ajax操作的写法是:

    $.ajax({})
      .done(function(){ alert("success!"); })
      .fail(function(){ alert("fail!"); });

    两种写法对比可以很明显的看出来,done()相当于传统ajax操作的success方法,fail()相当于传统ajax操作的fail方法。相对于传统的写法,代码可读性提高了。

    3.deferred对象的方法

    3.1基本用法

    (1).生成deferred对象

    var dfd = $.Deferred(); //create a deferred object

    (2).deferred对象的状态

    deferred对象有三种状态

    • pending:表示操作处于未完成的状态,任何deferred(延迟)对象开始于pending状态。
    • resolved:表示操作成功。
    • rejected:表示操作失败。

    state()方法返回deferred对象的当前状态。

    $.Deferred().state(); // 'pending'
    $.Deferred().resolve().state(); // 'resolved'
    $.Deferred().reject().state(); // 'rejected'

    (3).改变deferred对象的状态

    调用deferred.resolve() 或者 deferred.resolveWith()转换Deferred(递延)到resolved(解决)的状态,并立即执行设置中任何的doneCallbacks

    var callbackFunc = function(){console.log(arguments[0]);}
    var dfd = $.Deferred();
    dfd.done(callbackFunc);
    dfd.resolve("hello");  //'hello'

    调用deferred.reject() 或者 deferred.rejectWith()转换Deferred(递延)到rejected(拒绝)的状态,并立即执行设置中任何的failCallbacks

    var callbackFunc = function(){console.log(arguments[0]);}
    var dfd = $.Deferred();
    dfd.fail(callbackFunc);
    dfd.reject("fail");  //'fail'

    (4).绑定回调函数

    deferred对象状态改变的时候,会触发回调函数。任何回调使用deferred.then()deferred.always()deferred.done()或者 deferred.fail()添加到这个对象都是排队等待执行。

    • pending-->resolved,执行设置中任何的doneCallbacks(done()指定),参数由resolved传递给doneCallbacks。
    • pending-->rejected,执行设置中任何的failCallbacks(fail()指定),参数由resolved传递给failCallbacks。
    • pending-->resolved/rejected,执行always()指定的callbacks,参数由resolved传递给callbacks。
    var f1 = function(){console.log("done");}, 
         f2 = function(){console.log("fail");}, 
         f3 = function(){console.log("always");};
    
    var dfd = $.Deferred();
    dfd.done(f1).fail(f2).always(f3);
    
    //if
    dfd.resolve(); //'done' 'always'
    //if
    dfd.reject(); //'fail' 'always'

    如果在状态更改后附件一个callback则会立即执行callback,因此不必担心deferred对象何时被resolved或者rejected,因为无论何时,参数都会正确地传递给callbacks。

    var fun1 = function(){console.log(arguments[0]);},
        fun1 = function(){console.log(arguments[0]);};
    var dfd = $.Deferred();
    dfd.done(fun1);
    dfd.resolve("hello"); //'hello'
    dfd.done(fun2); //'hello'

    3.2.deferred对象的方法

    (1)$.Deferred([beforeStart]) -- 创建一个deferred对象,参数类型为Function,是一个在构造函数之前调用的函数。

    var func = function(){console.log("start");} 
    var dfd = $.Deferred(func); //'start'  create a deferred object

    (2)deferred.done(doneCallbacks [,doneCallbacks]) -- 当deferred(延迟)对象解决时,调用添加处理程序。

    args:接受一个或者多个参数,所有的参数都可以是一个单一的函数或者函数数组,当deferred(延迟)对象解决时,doneCallbacks被调用。回调是依照他们添加的顺序执行的。

    var func1 = function(){console.log("1");},
         func2 = function(){console.log("2");},
         func3 = function(){console.log("3");};
    var dfd = $.Deferred();
    dfd.done([func1,func2],func3,[func2,func1]);
    dfd.resolve(); // "1 2 3 2 1"

    (3)deferred.fail(failCallbacks [,failCallbacks]) -- 当deferred(延迟)对象拒绝时,调用添加处理程序。

    args:接受一个或者多个参数,所有的参数都可以是一个单一的函数或者函数数组,当deferred(延迟)对象拒绝时,failCallbacks被调用。回调是依照他们添加的顺序执行的。

    var func1 = function(){console.log("1");},
         func2 = function(){console.log("2");},
         func3 = function(){console.log("3");};
    var dfd = $.Deferred();
    dfd.fail([func1,func2],func3,[func2,func1]);
    dfd.reject(); // "1 2 3 2 1"

    (4)deferred.resolve(args) and deferred.resolveWith(context [,args]) -- 解决Deferred(延迟)对象,并根据给定的args参数(resolveWith给定context)调用任何doneCallbacks。

    参数:args -- type(object),传递给回调函数(doneCallbacks)的可选的参数,

            context -- type(object),Context(上下文)作为this对象传递给完成回调函数(doneCallbacks)。

    var func = function(arg){console.log(arg);};
    $.Deferred().done(func).resolve("done!"); //'done!'
    var func = function(arg1,arg2){console.log(arg1.name + ',' + arg2);};
    $.Deferred().done(func).resolve({name:'Lucy'},'How are you!'); // 'Lucy,How are you!'

    resolve和resolveWith的区别就等同于fire和fireWith的区别。

    var func = function () {
        console.log(this.name + ',' + arguments[0] + ' ' + arguments[1] + ' ' + arguments[2]);
    };
    $.Deferred().done(func).resolveWith({ name: "Lucy" }, ["How", "are", "you!"]);//'Lucy,How are you!'

    (5)deferred.reject(args) and deferred.rejectWith(context [,args]) -- 拒绝Deferred(延迟)对象,并根据给定的args参数(rejectWith给定context)调用任何failCallbacks。

    参数:args -- type(object),传递给回调函数(doneCallbacks)的可选的参数,

            context -- type(object),Context(上下文)作为this对象传递给完成回调函数(doneCallbacks)。

    var func = function(arg){console.log(arg);};
    $.Deferred().fail(func).reject("error!"); //'error!'
    var func = function(ctx,arg){console.log(ctx.name + ',' + arg);};
    $.Deferred().fail(func).reject({name:'Mark'},'What happend!'); // 'Mark,What happend!'

    reject和rejectWith的区别就等同于fire和fireWith的区别。

    var func = function () {
        console.log(this.name + ',' + arguments[0] + ' ' + arguments[1]);
    };
    $.Deferred().fail(func).rejectWith({ name: "Mark" }, ["what", "happend!"]); // 'Mark,What happend!'

    (6)deferred.promise([target]) -- 返回Deferred(延迟)的Promise(承诺)对象。

     参数可选,无参数时返回一个Promise(承诺)对象,Promise(承诺)对象仅会暴露那些需要绑定额外的处理或判断状态的延迟方法(thendonefailalways,pipeprogressstate,和 promise)时,并不会暴露任何用于改变状态的延迟方法(resolverejectnotify,resolveWithrejectWith, 和 notifyWith)。使用Promise(承诺)会阻止其他人破坏你制造的promise。

    function asyncEvent() {
          var dfd = jQuery.Deferred();
    
           // Resolve after a random interval
           setTimeout(function () {
                 dfd.resolve("hurray");
           }, Math.floor(400 + Math.random() * 2000));
    
            // Reject after a random interval
            setTimeout(function () {
                  dfd.reject("sorry");
            }, Math.floor(400 + Math.random() * 2000));
    
            // Show a "working..." message every half-second
            setTimeout(function working() {
                  if (dfd.state() === "pending") {
                        dfd.notify("working... ");
                         setTimeout(working, 500);
                    }
              }, 1);
    
               // Return the Promise so caller can't change the Deferred
               return dfd.promise();
     }
    
    // Attach a done, fail, and progress handler for the asyncEvent
    $.when(asyncEvent()).then(
           function (status) {
                 alert(status + ", things are going well");
           },
           function (status) {
                  alert(status + ", you fail this time");
           },
           function (status) {
                  alert(status);
           }
    );

    有参数时,会将事件绑定到参数上,然后返回该参数对象(返回的实际是一个扩展的Promise(承诺)对象)。

    var obj = {
        hello: function (name) {
            alert("Hello " + name);
        }
    },
    // Create a Deferred
    dfd = $.Deferred();
    
    // Set object as a promise
    dfd.promise(obj);
    
    // Resolve the deferred
    dfd.resolve("John");
    
    // Use the object as a Promise
    obj.done(function (name) {
         obj.hello(name); // will alert "Hello John"
    }).hello("Karl");

    (7)$.when(deferreds) -- 提供一种方法来执行一个或多个对象的回调函数。

    参数:type(Deferred),一个或多个延迟对象,或者普通的JavaScript对象。

    1. 参数仅传入一个单独的Deferred对象,返回它的Promise对象。
      function func() {
          var dfd = $.Deferred();
          setTimeout(function () {
              dfd.resolve("hurry");
          }, 500);
          return dfd.promise();
      };
      
      $.when(func()).done(function (arg) {
          alert(arg); /*alert "hurry"*/
      });
    2. 参数传入一个非Deferred和Promise对象,那么该参数会被当成一个被解决(resolved)的延迟对象,并且绑定到上面的任何doneCallbacks都会被立即执行。
      $.when( { name: 123 } ).done(
          function(arg) { alert(arg.name); } /* alerts "123" */
      );
    3. 无参数,返回一个resolved(解决)状态的Promise对象。
      $.when().state(); // "resolved"
    4. 参数为多个Deferred对象,该方法根据一个新的“宿主” Deferred(延迟)对象,跟踪所有已通过Deferreds聚集状态,返回一个Promise对象。当所有的延迟对象被解决(resolve)时,“宿主” Deferred(延迟)对象才会解决(resolved)该方法,或者当其中有一个延迟对象被拒绝(rejected)时,“宿主” Deferred(延迟)对象就会reject(拒绝)该方法。
      var d1 = $.Deferred();
      var d2 = $.Deferred();
       
      $.when( d1, d2 ).done(function ( v1, v2 ) {
          console.log( v1 ); // "Fish"
          console.log( v2 ); // "Pizza"
      });
       
      d1.resolve( "Fish" );
      d2.resolve( "Pizza" );

    (8)deferred.then(doneFilter [,failFilter] [,progressFilter]) -- 当Deferred(延迟)对象解决,拒绝或仍在进行中时,调用添加处理程序。

    参数:

    • doneFilter --   type(Function),当Deferred(延迟)对象得到解决时被调用的一个函数。
    • failFilter --   type(Function),当Deferred(延迟)对象拒绝时被调用的一个函数,可选。
    • progressFilter --   type(Function),当Deferred(延迟)对象生成进度通知时被调用的一个函数,可选。

    其实,then方法可以理解成,把done(),fail(),progress()合在一起写。

    var filterResolve = function () {
         var dfd = $.Deferred(),
              filtered = dfd.then(function (value) { return value * 2; });
          dfd.resolve(5);
          filtered.done(function (value) { console.log(value); });
    };
    filterResolve(); //'10'
    
    var defer = $.Deferred(),
         filtered = defer.then(null, function (value) {
             return value * 3;
          });
    
    defer.reject(6);
    filtered.fail(function (value) {
          alert("Value is 3*6 = " + value);
    });

    (9)deferred.always(alwaysCallbacks [,alwaysCallbacks]) -- 当Deferred(延迟)对象解决或拒绝时,执行alwaysCallbacks。

     顾名思义,只要Deferred对象的状态发生更改(解决或者拒绝)均会调用alwaysCallbacks。

    (10)deferred.state() -- 获取一个Deferred(延迟)对象的当前状态,不接受任何参数。

    $.Deferred().state();//"pending"

    上面讲述过deferre(延迟)对象的三种状态,这个方法对于debug非常有用,例如,在准备reject一个deferred对象之前,判断它是否处于resolved状态。

    (11)deferred.notify(args) and deferred.notifyWith()

    (12)deferred.progress()

    (13)deferred.pipe()

    (14).promise()

    (15)deferred.isRejected() 和 deferred.isResolved() --  从jQuery 1.7开始被弃用,较新版本的jQuery类库中已经被删除,可以使用state()方法代替这两个方法。

    (16)deferred.pipe() -- 从jQuery 1.8开始被弃用。

    4.什么情况下使用deferred对象和Promises?

    上面讲了很多,那么我们究竟在什么情况下使用Deferred对象和Promises对象呢?

    (1)复杂的动画

    不知道动画什么时候结束,但是又必须在动画结束的时候做一些操作或者是启动其他的动画,这种情况下,如果采用其他的方式,很容易导致代码可读性差,尤其是还夹带着一些其它的操作,比如渲染、表单操作等,现在jQuery会为你的动画操作返回一个Promise,这样这些动画可以进行链式操作。

    (2)处理队列

    window.queue = $.when() $('#list').on('click', function() { window.queue = window.queue.then(function() { //do the thing }) } )

    (3)The Wait promise

    function wait(ms) { 
        var deferred = $.Deferred(); 
        setTimeout(function(){deferred.resolve()}, ms);
        return deferred.promise(); 
    } 
    
    wait(1500).then(function () {
           // After 1500ms this will be executed 
    });

    (4)典型的Ajax操作

    $.when($.ajax({}), $.ajax({}))
      .done(function(){ alert("success!"); })
      .fail(function(){ alert("error!"); });

    (5)一些耗时的大循环操作

    5.参考链接

    [1] Graham Jenson, JQuery Promises and Deferreds: I promise this will be short

    [2] 阮一峰, jQuery的deferred对象详解

    [3]jQuery API, Deferred Object

    [4]José F. Romaniello, Understanding JQuery.Deferred and Promise

    [5]Matt Baker, jQuery.Deferred is the most important client-side tool you have

  • 请您注意

    ·自觉遵守:爱国、守法、自律、真实、文明的原则

    ·尊重网上道德,遵守《全国人大常委会关于维护互联网安全的决定》及中华人民共和国其他各项有关法律法规

    ·严禁发表危害国家安全,破坏民族团结、国家宗教政策和社会稳定,含侮辱、诽谤、教唆、淫秽等内容的作品

    ·承担一切因您的行为而直接或间接导致的民事或刑事法律责任

    ·您在编程中国社区新闻评论发表的作品,本网站有权在网站内保留、转载、引用或者删除

    ·参与本评论即表明您已经阅读并接受上述条款

  • 感谢本文作者
  • 作者头像
  • 昵称:往事如风
  • 加入时间:2013/5/16 0:00:00
  • TA的签名
  • 这家伙很懒,虾米都没写
  • +进入TA的空间
  • 以下内容也很赞哦
分享按钮