$.when用于监听多个 Deferred对象resolve,reject,notify的一个东西,今天我们就来看看它是怎么玩的。

    基本使用

    我们先来看一个最简单的demo:

    1. var d1=$.Deferred(),d2=$.Deferred();
    2. d1.done(function(value){
    3.     console.log('done1',value);
    4. });
    5. d2.done(function(value){
    6.     console.log('done2',value);
    7. });
    8. $.when(d1,d2).done(function(value1,value2){
    9.     console.log('when done',value1,value2);
    10. });
    11. d1.resolve('aa');
    12. d2.resolve('bb');
    13. /*
    14. done1 aa
    15. done2 bb
    16. when done aa bb
    17. */

    我们定义了两个Deferred对象,然后用$.when等待他们同时完成,我们发现当两个Deferred对象都完成的时候,$.when的done也被执行了,那么这背后的原理是什么呢?        

           

    首先当有两个或两个以上的Deferred对象的时候,$,when会额外创建一个Deferred对象用来监听他们的动作。

    $.when会在每个Deferred对象里的resolve队列,reject队列,progress队列里面添加一个方法:

    resolve队列:如果个数满足,调用$.when的resolve。

    reject队列:直接调用$.when的reject。

    progress队列:直接调用$.when的notify。

    $.when里面含有非Deferred对象

    当$.when里面含有非Deferred对象的时候,是不会计算他们的,例如有5个参数,三个不是Deferred对象,那么会额外创建一个Deferred对象去监视其中的两个Deferred对象,就和上面的情况一样了。

    那么假如可怜到一个都不是Deferred对象怎么办呢?那么这个时候$.when还是会创建一个Deferred对象,只不过$.when发现没有Deferred对象,立马执行了resolve而已。

    1. $.when().done(function(){
    2.     console.log('done');
    3. });

    $.when里面有且只有一个Deferred参数

    当$.when里面有且只有一个参数的时候,是不会额外创建一个Deferred对象的,这个时候所有的done,fail,progress都是挂在那个Deferred对象上的。

           

    源码如下:        

    1. when: function( subordinate /* , ..., subordinateN */ ) {
    2.         var i = 0,
    3.             resolveValues = slice.call( arguments ),
    4.             length = resolveValues.length,
    5.             // the count of uncompleted subordinates
    6.             remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
    7.             // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
    8.             deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
    9.             // Update function for both resolve and progress values
    10.             updateFunc = function( i, contexts, values ) {
    11.                 return function( value ) {
    12.                 //这里的value就是resolve等方法传入的参数!如果调用的是done方法,那么就会把resolveContext传递过来
    13.                 //这个context里面会放入when方法传入的参数,也就是多个Deferred对象
    14.                     contexts[ i ] = this;
    15.                     values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
    16.             //除非外面调用的notify否则不可能相等,因为只有progress会把progressValue传递进来,从而数组引用相同,否则不可能相等!
    17.             //如果不相等就表示调用的是resolve,如果相等那么就表示调用的是notify!这种用数组相等来判断是那种调用的方法极其巧妙!
    18.                     if ( values === progressValues ) {
    19.                         deferred.notifyWith( contexts, values );
    20. //如果不想等那么表示是resolve那么把remiaining减少,如果没有减少到0,那么就不会调用外层的Deferred对象的resolveWith方法
    21.                     //只有减少为0的时候才会调用外层的resolveWith方法!
    22.                     } else if ( !(--remaining) ) {
    23.         //(1)这里就可以说明上面的测试代码4的情况,为什么每一个Deferred对象resolve的参数参数会被传递到总的Deferred对象的done
    24.         //方法或者progress方法中,因为所有Deferred对象的回调函数updateFunc()会把参数传递到values[i]中,把每次传递的一个参数
    25.         //作为这个数组的一个元素,于是最后这个数组就会存在,当总的Deferred已经resolve,也就是remaining是0的时候就会
    26.         //调用他,底层调用的是Callbacks对象的fireWith
    27.         //(2)至于上面的if语句,每一个Deferred对象调用notify那么if语句总是满足的,于是都会调用notifyWith.同时要记住:
    28.         //notifyWith在Callbacks对象里面是用memory构造的,所以会记住上一次的参数结果,当第一次notify("aa")那么aa会被记忆
    29.                     //下次再次notify的时候还是用旧的参数调用新的函数!
    30.                         deferred.resolveWith( contexts, values );
    31.                     }
    32.                 };
    33.             },
    34.             progressValues, progressContexts, resolveContexts;
    35.         // add listeners to Deferred subordinates; treat others as resolved
    36.         if ( length > 1 ) {
    37.         //length是几,那么创建的数组长度就是几!
    38.         //有什么作用?
    39.         //(1)在updateFunc用数组的引用判断是那种调用类型,对于notify类型,那么调用progress那么传入的参数是progressContexts,
    40.         //progressValues于是在updateFunc中values就是progressValues,也就是两者相等那么表示要调用总的Deferred对象的progress方法!
    41.     //于是调用nodifyWith方法,上下文就是Deferred对象,第二个参数就是values[i]也就是notify函数调用时候传递的参数,如dfd.notify("xxx")
    42.     //那么values[i]="xxx",这个"xxx"会传入到when函数的progress方法中作为参数!
    43. //(2)在updateFunc中会用这些数组保存所有的notify或者resolve或者reject调用时候的参数,从而把这个参数数组封装到done,fail,progress回调函数中!
    44.             progressValues = new Array( length );
    45.             progressContexts = new Array( length );
    46.             resolveContexts = new Array( length );
    47.             for ( ; i < length; i++ ) {
    48.             //如果不是Deferred对象那么直接把remaining长度减一!
    49.                 if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
    50.                     resolveValues[ i ].promise()
    51.                     //resolveValues[ i ].promise()就是arguments[0]或者arguments[1]
    52. //done就是arguments[i]的done方法!也就是给这个arguments[i]对应的Callbacks中添加一个回调函数,当这个when参数,也就是传入的Deferred对象
    53.         //resolve以后就会回调这个函数,回调这个函数就是去执行updateFunc函数,在这个函数里面对remaining进行修改!
    54.                         .done( updateFunc( i, resolveContexts, resolveValues ) )
    55.                     //只要有一个未完成,那么就触发新创建的deferred的reject方法,进而调用when后面的fail方法!
    56.                         .fail( deferred.reject )
    57.                         .progress( updateFunc( i, progressContexts, progressValues ) );
    58.                 } else {
    59.                     --remaining;
    60.                 }
    61.             }
    62.         }
    63.         // if we're not waiting on anything, resolve the master
    64.         if ( !remaining ) {
    65.             deferred.resolveWith( resolveContexts, resolveValues );
    66.         }
    67.         return deferred.promise();

    源码里先是判断是否有唯一的Deferred,如果是,那么就不创建新的Deferred了,否则就创建一个。

    然后给每个Deferred里面的三个队列分别注入一个方法,然他们和$.when创建的Deferred对象关联。

    当然如果一个Deferred对象都没有,那么就直接resolve了。

    最后

    常见的$.ajax返回的就是Deferred的promise,因此常见的用法是$.when($.ajax(/**/),$.get(/**/)).done(){}。

    回到顶部
    我要评论

    所有评论

      相关文章