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

基本使用

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

var d1=$.Deferred(),d2=$.Deferred();
d1.done(function(value){
    console.log('done1',value);
});
d2.done(function(value){
    console.log('done2',value);
});
$.when(d1,d2).done(function(value1,value2){
    console.log('when done',value1,value2);
});
d1.resolve('aa');
d2.resolve('bb');
/*
done1 aa
done2 bb
when done aa bb
*/

我们定义了两个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而已。

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

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

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

       

源码如下:        

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

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

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

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

最后

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

回到顶部
我要评论

所有评论

返回
邮箱:
绑定
取消
×

我要评论

回复:

昵称:(昵称不超过20个字)

图片:

邮箱:
绑定邮箱后,若有回复,会邮件通知。
提交
还可以输入500个字