jQuery源码分析(十): 异步助手when

HTML 2016-11-09

起步

when是jq提供一种方法来执行异步对象的函数。

$.when(d1,d2,d3,d4......).done(function(v1, v2,v3...) {
    //等待所有异步加载完毕后执行
}); 

d1,d2,d3都是有规范的,都是通过Deferred产生的(如果不是,done()会立即执行)。

延时对象

$.ajax()其实也是用到了Deferred,所以它也属于延时对象:

$.when($.ajax("/page1.php"), $.ajax("/page2.php")).done(function(a1,  a2){
    //a1是第一个ajax返回的值
    //a2是第二个ajax返回的额值
    /* arguments are [ "success", statusText, jqXHR ] */
}

源码解析

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 ) {
                contexts[ i ] = this;
                values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
                if ( values === progressValues ) {
                    deferred.notifyWith( contexts, values );
                } else if ( !( --remaining ) ) {
                    deferred.resolveWith( contexts, values );
                }
            };
        },

        progressValues, progressContexts, resolveContexts;

    // Add listeners to Deferred subordinates; treat others as resolved
    if ( length > 1 ) {
        progressValues = new Array( length );
        progressContexts = new Array( length );
        resolveContexts = new Array( length );
        for ( ; i < length; i++ ) {
            if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
                resolveValues[ i ].promise()
                    .done( updateFunc( i, resolveContexts, resolveValues ) )
                    .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();
}

50行的代码,如果Deferred没理解好,这边也会很难理解。when().done()本身就是异步操作,所以内部构建了$.Deferred()对象。when的参数是多个异步对象,然后遍历每个异步对象给每一个对象绑定done、fail、progess方法,无非就是监听每一个异步的状态(成功,失败),如果是完成了自然会激活done方法。updateFunc()是监听方法,remaining是剩下要执行的数量:

else if ( !( --remaining ) ) {
    deferred.resolveWith( contexts, values );
}

当remaining为0时,才会执行$.when().done(fun)中 fun 方法。

为了好理解,简化下代码:

function when(subordinate) {
    var i = 0,
        resolveValues = [].slice.call(arguments),
        length = resolveValues.length,
        remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
        deferred = $.Deferred(),
        values = [], //收集resolve值
        len = length,
        updateFunc = function(value) {
            values.push(value);
            if (len === 1) {
                deferred.resolveWith('contexts', values);
            } else if ( !( --remaining ) ) {
                deferred.resolveWith( 'contexts', values );
            }
        };
    for(; i < length; i++) {
        resolveValues[i].done(updateFunc);
    }
    //如果参数不是延时对象
    if ( !remaining ) {
        deferred.resolveWith( resolveContexts, resolveValues );
    }
    return deferred.promise();
}

总结

when的设计依赖于Deferred内部处理的机制,嵌套了多层,思维很跳跃,不好掌握,因为绑定done、fail、progess方法是通过引用去实现的,一个代码片段执行完后跳到其他地方,再加上不是按照同步方式执行的,所以得多看多实验才能理解其中原理。


本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

如果对您有用,您的支持将鼓励我继续创作!