jQuery Deferred API(下)
发布在读懂jQuery2014年11月12日view:4119
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

上一篇,我们讲到了Deferred对象,让我们来看看如何使用它吧。

var de = $.Deferred();
de.done(function(){console.log('done');});
de.resolve();

好了,最简单的用法就是这样。但是在现实中,这么干完全没意义。那么,来点有意义的代码吧。

var ajax = $.ajax('/test.php');
ajax.done(function(data){
  // codes deal with the data returned.
  ...
});

这是Deferred对象(准确的说应该是promise对象)最常用的用法,非常实用,而且方便,能够为AJAX请求绑定多个回调函数。

再来看看复杂一点的情况,如果我们需要通过多个请求获取数据,然后同时对这些数据做一些操作呢?很自然得,我们会想到可以这么干:

$.ajax('/test1.php').done(function(data){
  var data1 = data;
  $.ajax('/test2.php').done(function(data){
    // deal with data1 and data here.
    ...
  });
});

我们可以尽情的嵌套AJAX请求,jQuery都能正常执行。但是这么干有个缺陷,就是浪费时间,这两个请求之间是没有依赖的,这样写仿佛第二个请求依赖第一个请求的结果了,本来可以并行的请求,现在只能通过串行的方式了。

这个时候,我们的$.when()闪亮登场了。先来看看jQuery的文档怎么说:

Provides a way to execute callback functions based on one or more objects, usually Deferred objects that represent asynchronous events.

所以上面的例子可以改为:

$.when($.ajax('/test1.php'),$.ajax('/test2.php')).done(function(data1, data2){
  // here comes the code
})

哈哈,比上一种写法还简单,而且两个AJAX请求之间没有依赖关系,既符合逻辑,又能节省时间,不错。


看完了用法,让我们再来看看源码吧。

when: function( subordinate /* , ..., subordinateN */ ) {
  var i = 0,
    // 这里把参数变成标准数组,这里,我们暂且把每个参数称作延迟子任务
    // 这里的resolveValues有可能不是Deferred对象,可能是简单的JS对象
    resolveValues = core_slice.call( arguments ),
    // 获取参数列表长度
    length = resolveValues.length,

    // 获取未完成的子任务的数量
    remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

    // 这里的deferred是主Deferred对象,在所有的子任务之上。如果只有一个子任务,那么deferred就等于这个子任务
    deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

    // 重要方法,让我们先略过,最后再来看这里
    // 用于resolve和notify操作
    updateFunc = function( i, contexts, values ) {
      // updateFunc返回一个函数,这个函数会被加入到所有延迟子任务的resolve和notify回调函数列表中
      // 一旦某个延迟子任务调用了resolve()或者notify(),就会执行此方法,达到监听的目的
      return function( value ) {
        // 注意这个方法是通过Deferred对象的resolve方法调用的,
        // 因此,这里的this指向的是某个延迟子任务,即Deferred对象
        // 而contexts即是通过updateFunc传入的progressContexts/resolveContexts
        contexts[ i ] = this;
        // 同理,values即resolveValues/progressValues
        // 如果是AJAX请求,则将每个AJAX请求返回的结果放入数组中
        values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
        // 通过updateFunc传入的values和progressValues对比,判断当前是在notify还是resolve状态
        // 注意这里notifyWith和resolveWith的上下文和参数都是数组,
        // 每个数组元素依次对应于$.when()方法传入的Deferred对象
        // 或者Deferred对象resolve/notify的参数列表
        if( values === progressValues ) {
          // 如果是notify,则任意延迟子任务都会触发主延迟对象的notify状态
          deferred.notifyWith( contexts, values );
        // 如果是resolve被触发,那么等待的延迟子任务数减一
        // 如果这是最后一个延迟子任务,即表示所有延迟子任务都已完成
        // 则触发主延迟对象的resolve方法
        } else if ( !( --remaining ) ) {
          deferred.resolveWith( contexts, values );
        }
      };
    },
    // 保存上下文和progress值的数组
    progressValues, progressContexts, resolveContexts;

  // 为延迟子任务添加监听器,其他则当做resolved状态
  if ( length > 1 ) {
    // 这三个数组分别代表:
    // 1. notify时传给progress回调函数的参数数组
    // 2. notify时传给progress回调函数的上下文数组
    // 3. resolve时传给done回调函数的上下文数组
    // 三个数组的长度为延迟子任务的数量
    progressValues = new Array( length );
    progressContexts = new Array( length );
    resolveContexts = new Array( length );
    // 对参数列表进行遍历
    for ( ; i < length; i++ ) {
      // 如果是延迟子任务,则监听
      if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
        // 任意一个延迟子任务到达rejected状态,都会触发主延迟对象的reject方法。
        // 通过updateFunc监听所有延迟子任务的resolved状态和notify状态,注意两者参数不同
        resolveValues[ i ].promise()
          .done( updateFunc( i, resolveContexts, resolveValues ) )
          .fail( deferred.reject )
          .progress( updateFunc( i, progressContexts, progressValues ) );
      // 如果不是deferred对象,则当做已经resolved处理,remaining减一
      } else {
        --remaining;
      }
    }
  }

  // 如果没有需要等待的延迟子任务,则直接resolve主延迟对象
  if ( !remaining ) {
    deferred.resolveWith( resolveContexts, resolveValues );
  }

  // 这里返回promise对象,避免使用者改变主延迟对象状态
  // 接下来我们看看updateFunc吧
  return deferred.promise();
}

通过查看updateFunc()的代码我们可以知道,通过$.when().[done|fail|progress]()方法添加的回调函数内的this是所有Deferred对象的数组,参数是对应Deferred对象回调函数参数的数组,在使用$.when()时,注意这一点即可。

好了,通过两篇文章,我们已经把Deferred对象和Deferred Helper给弄明白了,结合之前的Callback解析,jQuery的Deferred部分,算是解读完成了。下一站,出发!

小广告:我的博客 YuuuuC.me #^_^#

评论
发表评论
6年前

@前端乱炖 乱炖在这儿找人吗?

6年前

LZ找工作么。。。

WRITTEN BY
YuC_C
陈胜后裔吗?博客:lovecicy.com
TA的新浪微博
PUBLISHED IN
读懂jQuery

用了这么久jQuery了,想看看到底jQuery怎么写的,为什么这么牛呢。本栏解读的版本为1.8.1,本人(小Y)才疏学浅,如有疏漏、错误之处,还望各位大牛雅正。个人博客:YuuuuC

我的收藏