四、异步队列Deferred Object2——jQuery.Deferred(func)

方法jQuery.Deferred(func)在jQuery 1.5中引入,在jQuery 1.7中重新实现——改为基于jQuery.Callbacks(flags)实现,该方法返回一个链式工具对象,支持添加多个回调函数到回调函数列表、触发回调函数列表、传播任意同步或异步任务的成功或失败状态等功能。在后文中,把返回的链式工具对象称为“异步队列”。

方法jQuery.Deferred(func)在jQuery.Callbacks(flags)的基础上,为回调函数增加了状态,提供了多个回调函数列表。可以通过调用方法deferred.done/fail/progress/then/always()添加成功回调函数、失败回调函数、消息回调函数到对应的成功、失败、消息回调函数列表中,并且可通过调用方法deferred.resolve/resolveWith/rejectWith/notify/notifyWith()分别触发成功、失败、消息回调函数列表。

异步队列又三种状态:待定(pending)、成功(resolved)和失败(rejected)。初始时处于待定状态(pending);调用方法deferred.resolve(args)或resolveWith(context, args)将改变异步队列的状态为成功状态(resolved),并且立即执行添加的所有成功回调函数;调用方法deferred.reject(args)或rejectWith(context, args)则改变异步队列的状态为失败状态(rejected),并且立即执行设置的所有失败回调函数。一旦异步队列进入成功或失败状态,就会保持它的状态不变,例如,再次调用deferred.resolve()或deferred.reject()会被忽略。

异步队列进入成功或失败状态后,仍然可以通过调用deferred.done()、deferred.fail()、deferred.then()、deferred.always()添加更多的回调函数,这些回调函数会被立即执行,并传入之前调用方法deferred。sesolve(args)、deferred.resolveWith(context, args)、deferred.reject(args)或deferred.rejectWith(context, args)时传入的参数。

异步队列基于规范CommonJSPromises/A实现,解耦了异步任务和回调函数。当jQuery方法返回一个异步队列或支持异步队列功能的对象时,例如,jQuery.ajax()或jQuery.when(),很多情况下我们只是想要使用方法deferred.then()、deferred.done()和deferred.fail()添加回调函数到异步队列中,而在创建异步队列的代码内部,将会在某个时间点调用deferred.resolve()或deferred.reject()使合适的回调函数执行。

// 代码行3420——3425
function Identity( v ) {
    return v;
}
function Thrower( ex ) {
    throw ex;
}

// 代码行:3460——3756

jQuery.extend( {

    Deferred: function( func ) {
        var tuples = [

                // action, add listener, callbacks,
                // ... .then handlers, argument index, [final state]
                // once表示回调函数列表只能被触发一次,memory表示会记录上一次触发回调函数列表时的参数,触发后添加的任何回调函数都将用记录的参数值立即调用。
                    // 添加消息回调函数
                [ "notify", "progress", jQuery.Callbacks( "memory" ),
                    jQuery.Callbacks( "memory" ), 2 ],
                    // 添加成功回调函数
                [ "resolve", "done", jQuery.Callbacks( "once memory" ),
                    jQuery.Callbacks( "once memory" ), 0, "resolved" ],
                    // 添加失败回调函数
                [ "reject", "fail", jQuery.Callbacks( "once memory" ),
                    jQuery.Callbacks( "once memory" ), 1, "rejected" ]
            ],
            // 初始状态。异步队列的初始状态为待定pending。变量state在调用方法deferred.resolve()或deferred.resolveWidth()后被设置为字符串“resolved”,表示成功状态,调用方法deferred.reject()或deferred.rejectWith()后被设置为字符串“rejected”,表示失败状态。
            state = "pending",
            // 异步队列的只读副本
            promise = {
                // deferred.state()用于返回异步队列的状态。
                // 如果方法deferred.state()返回“resolved”,意味着deferred.resolve()或者deferred.resolveWith()已经被调用,成功回调函数已经执行或者正在执行;如果方法deferred.state()返回“rejected,意味着deferred.reject()或者deferred.rejectWith()已经被调用,并且失败回调函数已经执行或者正在执行;
                state: function() {
                    return state;
                },
                // 在便捷方法always()中,采用链式语法依次调用方法done()、fail()重复添加回调函数,并返回this以继续支持链式语法。调用方法done()、fail()时,关键字this始终指向当前异步队列,因此实际上不借用方法apply()也可以用链式语法。
                always: function() {
                    deferred.done( arguments ).fail( arguments );
                    return this;
                },
                // 捕捉
                "catch": function( fn ) {
                    return promise.then( null, fn );
                },

                // Keep pipe for back-compat
                // 工具方法deferred.pipe()用于过滤当前异步队列的状态和参数,并返回一个新的异步队列的只读副本。当前异步队列被触发时,过滤函数将被调用并把返回值给只读副本。
                pipe: function( /* fnDone, fnFail, fnProgress */ ) {
                    var fns = arguments;

                    // 创建一个新的异步队列并传入一个函数作为参数,这个函数参数在新的异步队列返回前被调用。
                    return jQuery.Deferred( function( newDefer ) {
                        // 在函数参数执行过程中向当前异步队列添加了特殊的成功、失败和消息回调函数。
                        jQuery.each( tuples, function( i, tuple ) {

                            // Map tuples (progress, done, fail) to arguments (done, fail, progress)
                            var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];

                            // deferred.progress(function() { bind to newDefer or newDefer.notify })
                            // deferred.done(function() { bind to newDefer or newDefer.resolve })
                            // deferred.fail(function() { bind to newDefer or newDefer.reject })
                            deferred[ tuple[ 1 ] ]( function() {
                                // 当当前的异步队列被触发时,添加的特殊回调函数被调用,执行过滤函数,并检测过滤函数的返回值。
                                // 如果过滤函数又返回一个异步队列或支持异步队列功能的对象(用最新的异步队列表示),则把新的异步队列的方法resolve()、reject()、notify()添加到最新的异步队列中,当最新的异步队列被触发时,新的异步队列中相应状态的回调函数被调用,参数为最新的异步队列的参数。
                                // 如果过滤函数的返回值不是异步队列或不支持异步队列功能,新的异步队列中相应状态的回调函数被执行,参数为过滤函数的返回值。
                                var returned = fn && fn.apply( this, arguments );
                                if ( returned && isFunction( returned.promise ) ) {
                                    returned.promise()
                                        .progress( newDefer.notify )
                                        .done( newDefer.resolve )
                                        .fail( newDefer.reject );
                                } else {
                                    newDefer[ tuple[ 0 ] + "With" ](
                                        this,
                                        fn ? [ returned ] : arguments
                                    );
                                }
                            } );
                        } );
                        fns = null;
                    } ).promise();
                },
                // 便捷方法deferred.always(onFulfilled, onRejected, onProgress)用于将回调函数同时添加到成功回调函数列表doneList和失败回调函数列表failList,即保存两份引用,当异步队列处于成功(resolved)或失败(rejected)状态时被调用。

                // 在便捷方法then()中,采用链式语法依次调用方法onFulfilled(), onRejected(), onProgress()逐个添加回调函数,并返回this以继续支持链式语法。
                then: function( onFulfilled, onRejected, onProgress ) {
                    var maxDepth = 0;
                    function resolve( depth, deferred, handler, special ) {
                        return function() {
                            var that = this,
                                args = arguments,
                                mightThrow = function() {
                                    var returned, then;

                                    // Support: Promises/A+ section 2.3.3.3.3
                                    // https://promisesaplus.com/#point-59
                                    // Ignore double-resolution attempts
                                    if ( depth < maxDepth ) {
                                        return;
                                    }

                                    returned = handler.apply( that, args );

                                    // Support: Promises/A+ section 2.3.1
                                    // https://promisesaplus.com/#point-48
                                    if ( returned === deferred.promise() ) {
                                        throw new TypeError( "Thenable self-resolution" );
                                    }

                                    // Support: Promises/A+ sections 2.3.3.1, 3.5
                                    // https://promisesaplus.com/#point-54
                                    // https://promisesaplus.com/#point-75
                                    // Retrieve `then` only once
                                    then = returned &&

                                        // Support: Promises/A+ section 2.3.4
                                        // https://promisesaplus.com/#point-64
                                        // Only check objects and functions for thenability
                                        ( typeof returned === "object" ||
                                            typeof returned === "function" ) &&
                                        returned.then;

                                    // Handle a returned thenable
                                    if ( isFunction( then ) ) {

                                        // Special processors (notify) just wait for resolution
                                        if ( special ) {
                                            then.call(
                                                returned,
                                                resolve( maxDepth, deferred, Identity, special ),
                                                resolve( maxDepth, deferred, Thrower, special )
                                            );

                                        // Normal processors (resolve) also hook into progress
                                        } else {

                                            // ...and disregard older resolution values
                                            maxDepth++;

                                            then.call(
                                                returned,
                                                resolve( maxDepth, deferred, Identity, special ),
                                                resolve( maxDepth, deferred, Thrower, special ),
                                                resolve( maxDepth, deferred, Identity,
                                                    deferred.notifyWith )
                                            );
                                        }

                                    // Handle all other returned values
                                    } else {

                                        // Only substitute handlers pass on context
                                        // and multiple values (non-spec behavior)
                                        if ( handler !== Identity ) {
                                            that = undefined;
                                            args = [ returned ];
                                        }

                                        // Process the value(s)
                                        // Default process is resolve
                                        ( special || deferred.resolveWith )( that, args );
                                    }
                                },

                                // Only normal processors (resolve) catch and reject exceptions
                                process = special ?
                                    mightThrow :
                                    function() {
                                        try {
                                            mightThrow();
                                        } catch ( e ) {

                                            if ( jQuery.Deferred.exceptionHook ) {
                                                jQuery.Deferred.exceptionHook( e,
                                                    process.stackTrace );
                                            }

                                            // Support: Promises/A+ section 2.3.3.3.4.1
                                            // https://promisesaplus.com/#point-61
                                            // Ignore post-resolution exceptions
                                            if ( depth + 1 >= maxDepth ) {

                                                // Only substitute handlers pass on context
                                                // and multiple values (non-spec behavior)
                                                if ( handler !== Thrower ) {
                                                    that = undefined;
                                                    args = [ e ];
                                                }

                                                deferred.rejectWith( that, args );
                                            }
                                        }
                                    };

                            // Support: Promises/A+ section 2.3.3.3.1
                            // https://promisesaplus.com/#point-57
                            // Re-resolve promises immediately to dodge false rejection from
                            // subsequent errors
                            if ( depth ) {
                                process();
                            } else {

                                // Call an optional hook to record the stack, in case of exception
                                // since it's otherwise lost when execution goes async
                                if ( jQuery.Deferred.getStackHook ) {
                                    process.stackTrace = jQuery.Deferred.getStackHook();
                                }
                                window.setTimeout( process );
                            }
                        };
                    }

                    return jQuery.Deferred( function( newDefer ) {

                        // progress_handlers.add( ... )
                        tuples[ 0 ][ 3 ].add(
                            resolve(
                                0,
                                newDefer,
                                isFunction( onProgress ) ?
                                    onProgress :
                                    Identity,
                                newDefer.notifyWith
                            )
                        );

                        // fulfilled_handlers.add( ... )
                        tuples[ 1 ][ 3 ].add(
                            resolve(
                                0,
                                newDefer,
                                isFunction( onFulfilled ) ?
                                    onFulfilled :
                                    Identity
                            )
                        );

                        // rejected_handlers.add( ... )
                        tuples[ 2 ][ 3 ].add(
                            resolve(
                                0,
                                newDefer,
                                isFunction( onRejected ) ?
                                    onRejected :
                                    Thrower
                            )
                        );
                    } ).promise();
                },

                // Get a promise for this deferred
                // If obj is provided, the promise aspect is added to the object
                // 只读副本deferred.promise(obj)
                // 方法deferred.promise(obj)用于返回当前异步队列的只读副本,或为一个普通JavaScript对象增加只读副本中的方法并返回。只读副本值暴露了添加回调函数和判断状态的方法:done()、fail()、progress()、then()、always()、state()、pipe(),不包含触发执行和改变状态的方法:resolve()、reject()、notify()、resolveWith()、notifyWith()
                promise: function( obj ) {
                    // 如果没有传入参数obj,则返回当前异步队列的只读副本promise。
                    // 如果传入了参数obj,则把只读副本promise中的方法添加到参数obj中,从而使得参数obj具有异步队列行为,但是只包含了添加回调函数和判断状态的方法。
                    return obj != null ? jQuery.extend( obj, promise ) : promise;
                }
            },
            
            deferred = {};

        // Add list-specific methods
        // 添加触发成功、失败、消息回调函数列表的方法
        jQuery.each( tuples, function( i, tuple ) {
            var list = tuple[ 2 ],
                stateString = tuple[ 5 ];

            // promise.progress = list.add
            // promise.done = list.add
            // promise.fail = list.add
            promise[ tuple[ 1 ] ] = list.add;

            // Handle state
            if ( stateString ) {
                list.add(
                    function() {

                        // state = "resolved" (i.e., fulfilled)
                        // state = "rejected"
                        state = stateString;
                    },

                    // rejected_callbacks.disable
                    // fulfilled_callbacks.disable
                    tuples[ 3 - i ][ 2 ].disable,

                    // rejected_handlers.disable
                    // fulfilled_handlers.disable
                    tuples[ 3 - i ][ 3 ].disable,

                    // progress_callbacks.lock
                    tuples[ 0 ][ 2 ].lock,

                    // progress_handlers.lock
                    tuples[ 0 ][ 3 ].lock
                );
            }

            // progress_handlers.fire
            // fulfilled_handlers.fire
            // rejected_handlers.fire
            list.add( tuple[ 3 ].fire );

            // deferred.notify = function() { deferred.notifyWith(...) }
            // deferred.resolve = function() { deferred.resolveWith(...) }
            // deferred.reject = function() { deferred.rejectWith(...) }
            deferred[ tuple[ 0 ] ] = function() {
                deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
                return this;
            };

            // deferred.notifyWith = list.fireWith
            // deferred.resolveWith = list.fireWith
            // deferred.rejectWith = list.fireWith
            deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
        } );

        // Make the deferred a promise
        // 异步队列,只读副本promise中的方法
        promise.promise( deferred );

        // Call given func if any
        // 如果传入函数参数func,则调用。调用时指定上下文为异步队列deferred,并把异步队列deferred作为第一个参数传入,这样函数参数func在执行过程中仍然可以调用异步队列deferred的方法。
        if ( func ) {
            func.call( deferred, deferred );
        }

        // All done!
        // 返回异步队列deferred
        return deferred;
    }
} );

 

posted @ 2019-05-24 11:54  道鼎金刚  阅读(225)  评论(0)    收藏  举报