jQuery的deferred对象和promise对象

Promise是一种令代码异步行为更加优雅的抽象,有了它,我们就可以像写同步代码一样去写异步代码。jQuery从1.5版本开始实现了CommonJS Promise/A规范这一重量级方案,不过没有严格按照规范进行实现,有一些API上的差异。

1、deferred对象的方法:

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

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

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

  (4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。(也就是说promise对象不允许你调用resolve和reject方法,promise是deferred的只读版,或者更通俗地理解成promise是一个对将要完成的任务的承诺。

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

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

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

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

{I}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);
});
{II}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('

' + resp +'

');

});

$.when(maybeAsync(0)).then(function(resp){

    $('#target').append('

'+resp+'

');

});

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


  (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("已执行!");} );

(10)state方法

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

var deferred = new $.Deferred();

deferred.state();    //"pending"

deferred.resolve();

deferred.state();    //"resolved"

该方法的返回值有三个:

pendding:表示操作还没有完成

resolved:表示操作成功

rejected:表示操作失败

(11)notify()和progress()

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

var userProgress = $.Deferred();

var $profileField = $('input');

var totalFields = $profileField.length;

userProgress.progress(function(fieldFields){

    var pctComplete = (fieldFields/totalFields)*100;

    $('#progress').html(pctComplete.toFixed(0));

});

userProgress.done(function(){

    $('#thanks').html("谢谢完成profile").show();

});

$('#input').on('change',function(){

    var fieldFields = $profileFields.filter('[value!=''']').length;

    userProgress.notify(fieldFields);

    if(fieldFields == totalFields){

        userProgress.resolve();

    }

});

(12)pipe方法

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

    
(13)wait()方法

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

$wait = function(time){

    return $.Deferred(function(df){

       setTimeout(dfd.resolve,time);

     });

}

//使用方法如下:

$.wait(5000).then(function(){

  alert("Helo")

})

/*改写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:"http://search.twitter.com/search.json",
     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)

});



2、Promise对象

Promise 对象用来进行延迟(deferred) 和异步(asynchronous ) 计算。回顾一下在JQuery 1.5之前,传统的Ajax操作写法:

        
        
        
        
$.ajax({
url: "/ServerResource.txt",
success: successFunction,
error: errorFunction
});

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

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

        
        
        
        
var promise = $.ajax({
url: "/ServerResource.txt"
});
 
promise.done(successFunction);
promise.fail(errorFunction);
promise.always(alwaysFunction);

在jQuery 1.6之前,always()相当于complete(),在done() 或 fail()执行完毕之后才执行,即无论Ajax的执行结果是什么,always()总会执行。

done(), fail(), 和always()会返回同一个JQuery XMLHttpRequest(jqXHR)对象,所以可以进行链式调用:

        
        
        
        
$.ajax( "example.php" )
.done(function() { alert("success"); })
.fail(function() { alert("error"); })
.always(function() { alert("complete"); });

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

        
        
        
        
$.ajax("test.html")
  .done(function(){ alert("哈哈,成功了!");} )
  .fail(function(){ alert("出错啦!"); } )
  .done(function(){ alert("第二个回调函数!");} );

回调函数可以添加任意多个,它们按照添加顺序执行。如果在后续的代码中还需要利用改jqXHR对象,就必须用变量保存:

        
        
        
        
var jqxhr = $.ajax( "example.php" )
.done(function() { alert("success"); })
.fail(function() { alert("error"); })
.always(function() { alert("complete"); });
 
// todo ...
 
// 再次调用
jqxhr.always(function() { alert("another complete"); });

另外一种产生链式调用的方式是利用Promise的 then 方法,它接受三个event handlers作为参数,在jquery 1.8之前,对于多个回调函数,有需要以数组方式传入三个参数:

        
        
        
        
$.ajax({url: "/ServerResource.txt"})
.then([successFunction1, successFunction2, successFunction3],
[errorFunction1, errorFunction2]);
 
//same as
 
var jqxhr = $.ajax({
url: "/ServerResource.txt"
});
 
jqxhr.done(successFunction1);
jqxhr.done(successFunction2);
jqxhr.done(successFunction3);
jqxhr.fail(errorFunction1);
jqxhr.fail(errorFunction2);

1.8版本之后,then会返回一个新的Promise,它可以通过一个函数过滤掉Deferred对象的状态和值,用于取代不被推荐使用的 deferred.pipe() 方法。

        
        
        
        
var promise = $.ajax({
url: "/ServerResource.txt"
});
 
promise.then(successFunction, errorFunction);
 
//如果不想处理某个事件类型,可以传入Null
var promise = $.ajax({
url: "/ServerResource.txt"
});
 
promise.then(successFunction); //no handler for the fail() event

then()方法还能逐次调用多个方法,可以用于处理有着先后顺序或者依赖的多个Ajax请求:

        
        
        
        
var promise = $.ajax("/myServerScript1");
 
function getStuff() {
return $.ajax("/myServerScript2");
}
 
promise.then(getStuff).then(function(myServerScript2Data){
// Do something
});


Promise的状态

在任何时刻,Promise只能处于三种状态之一:未完成(unfulfilled)、已完成(resolved)和已失败(resolved)。promise默认的状态是unresolved,任何处于回调队列的函数都会被执行。举个粟子,如果一个Ajax调用成功,$.resolved会被调用,同时promise的状态转为resolved,以及任何监听done的回调都会被执行;相反,则$.rejected会被调用,同时promise的状态转为rejected,以及任何监听fail的回调都会被执行。

对上述的wait进行改写:

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

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

         
         
         
         
//wait()函数运行完,就会自动运行done()方法指定的回调函数。
$.when(wait(dtd))
  .done(function(){ alert("哈哈,成功了!"); })
  .fail(function(){ alert("出错啦!"); });

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

但是这种写法是有问题的,因为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(); //在这里改变dtd的执行状态

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

为了避免这种情况,可以在内部建立Deferred对象:

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

另外一个方式是利用 deferred.promise() 方法:

The deferred.promise() method allows an asynchronous function to prevent other code from interfering with the progress or status of its internal request. The Promise exposes only the Deferred methods needed to attach additional handlers or determine the state (then, done, fail, always, pipe, progress, state and promise), but not ones that change the state (resolve, reject, notify, resolveWith, rejectWith, and notifyWith).

也就是说,deferred.promise() 只是阻止其他代码来改变这个 deferred 对象的状态。可以理解成,通过deferred.promise() 方法返回的 deferred promise 对象,是没有 resolve ,reject, progress , resolveWith, rejectWith , progressWith 这些可以改变状态的方法,你只能使用 done, then ,fail 等方法添加 handler 或者判断状态。

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

此外,
(1)pipe()的使用注意
$(ajax1).pipe(value1).pipe(value2);     //这样子可以实现value2接受上一个promise对象($(ajax1).pipe(value1))的return值。但是如果上一个promise的返回值是一个promise对象的话,则会将value2的值传递给这个返回的promise对象。

/*pipe的深入理解:执行pipe时候会判断pipe执行的时候接受的参数,如果是一个值,则直接接受这个值,pipe就立即执行;如果是一个deferred/promise,pipe()则会等待这个deferred/promise执行后才会执行*/

 $.Deferred().resolve(1).promise()

      .pipe(function(a){

        /*a为promise对象*/

        console.log(a === 1) //输出true

        return $.Deferred().resolve(2).promise();

    }).pipe(function(b){

        /*b为promise对象*/

        console.log(b === 2) //输出true

    })



(2)对大多数Promise库的探讨
尽管Promise已经有自己的规范,但目前的各类Promise库,在Promise的实现细节上是有差异的,部分API甚至在意义上完全不同。但Promise的核心内容,是相通的,它就是then方法。在相关术语中,promise指的就是一个有then方法,且该方法能触发特定行为的对象或函数。
实现promise的级联:
要实现then的级联,就必须做到:
{I}   then方法必须返回promise。
{II}    这个返回的promise必须用传递给then方法的回调运行后的返回结果,来设置自己的值。
{III} 传递给then方法的回调,必须返回一个promise或值。

design/q4.js中,为了实现这一点,新增了一个工具函数ref:

?
1
2
3
4
5
6
7
8
9
var ref = function (value) {
   if (value && typeof value.then === "function" )
     return value;
   return {
     then: function (callback) {
       return ref(callback(value));
     }
   };
};

这是在着手处理与promise关联的value。这个工具函数将对任一个value值做一次包装,如果是一个promise,则什么也不做,如果不是promise,则将它包装成一个promise。注意这里有一个递归,它确保包装成的promise可以使用then方法级联。为了帮助理解它,下面是一个使用的例子:

?
1
2
3
4
5
6
ref( "step1" ).then( function (value){
   console.log(value); // "step1"
   return 15;
}).then( function (value){
   console.log(value); // 15
});
大部分Promise库会有更多的API来应对更多和Promise有关的需求,例如all()、spread(),不过,读到这里,你已经了解了实现Promise的核心理念,这一定对你今后应用Promise有所帮助。 Q作为一个典型Promise库,在思路上走得很明确。


你可能感兴趣的:(JS(ES),jQuery)