jQuery Deferred


Deferred是jQuery中對CommonJS的異步模型實現,旨在提供通用的接口,簡化異步編程難度。

其是一個可鏈式操作的對象,提供多個回調函數的注冊,以及回調列隊的回調,並轉達任何異步操作成功或失敗的消息。

由於其對jQuery Callbacks的依賴性,如果沒有概念的朋友可以查看jQuery Callbacks

 

jQuery.Deferred( [beforeStart ] )

創建一個Deferred對象。

beforeStart:

類型:  FunctionDeferred deferred )
一個在構造函數返回前運行的處理函數。

 

resolve、reject、notify

Defferred中定義了三種動作,resolve(解決)、reject(拒絕)、notify(通知),對應Callbacks對象的fire動作。

進而又提供了可以定義運行時的this對象的fire,即fireWith,所以又有擴展了三個對應的操作resolveWith、rejectWith、notifyWith。

內部對應的事件分別是:done(操作完成)、fail(操作失敗)、progress(操作進行中),也就是Callbacks對象的add方法添加監聽。

舉個簡單的例子,我們可以通過deferred.done注冊上一個動作完成后的,那么當有地方觸發了deferred.resolve或者deferred.resolveWith(這兩個方法的差別在於能不能定義回調函數的this對象)時,則回調注冊的函數。

其他對應的也是一樣的。

代碼上大概是這樣的:

var dtd = $.Deferred(); // 新建一個deferred對象
var wait = function(dtd){
    var tasks = function(){
        alert("執行完畢!");
        dtd.resolve(); // 改變deferred對象的執行狀態
        };
    setTimeout(tasks,5000);
    return dtd;
};

這樣我們就有了一個5000ms延遲的wait函數。於是我們就可以這么調用:

wait(dtd).done(function(){ alert("成功了!"); })
               .fail(function(){ alert("出錯啦!"); });

 

then

then方法提供了三種事件的注冊,只要按順序作為參數傳進去就可以了。

then: function( /* fnDone, fnFail, fnProgress */ ) {
    //分別對應完成后運行的函數,失敗后運行的函數,正在運行過程中運行的函數
    var fns = arguments;
    //返回一個新的Deferred的promise,then是上一個Deferred運行后才運行的
    return jQuery.Deferred(function( newDefer ) {
        //分別對不同狀態注冊函數
        jQuery.each( tuples, function( i, tuple ) {
            var action = tuple[ 0 ],    //取出動作名
            fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];    //取出對應回調函數
            // 分別對當前的Deferred對象注冊回調函數,也就是注冊deferred[ done | fail | progress ]
            deferred[ tuple[1] ](function() {
                var returned = fn && fn.apply( this, arguments );
                //如果傳進來的回調函數會返回Deferred對象則在該對象上注冊事件
                if ( returned && jQuery.isFunction( returned.promise ) ) {
                    returned.promise()
                        .done( newDefer.resolve )
                        .fail( newDefer.reject )
                        .progress( newDefer.notify );
                //否則對創建出來的newDefer執行對應事件
                } else {
                    //如果上一個函數有返回值則接受傳返回值,否則傳上一個Deferred傳來的參數
                    newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
                }
            });
        });
        fns = null;
    }).promise();
},

 

Promise

Promise只提供Deferred對象中的thendonefailalwayspipeisResolved, 和isRejected,防止用戶自行改變Deferred的狀態。

 

完整的Deferred

jQuery.Deferred = function( func ) {
    var tuples = [
            // 動作, 監聽事件, 回調函數列隊, 最終狀態
            [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
            [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
            [ "notify", "progress", jQuery.Callbacks("memory") ]
        ],
        state = "pending",
        //定義promise對象
        promise = {
            //返回當前狀態
            state: function() {
                return state;
            },
            //無論成功還是失敗都運行回調函數
            always: function() {
                deferred.done( arguments ).fail( arguments );
                return this;
            },
            then: function( /* fnDone, fnFail, fnProgress */ ) {
            //分別對應完成后運行的函數,失敗后運行的函數,正在運行過程中運行的函數
                var fns = arguments;
                //返回一個新的Deferred的promise,then是上一個Deferred運行后才運行的
                return jQuery.Deferred(function( newDefer ) {
                    //分別對不同狀態注冊函數
                    jQuery.each( tuples, function( i, tuple ) {
                        var action = tuple[ 0 ],    //取出動作名
                            fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];    //取出對應回調函數
                        // 分別對當前的Deferred對象注冊回調函數,也就是注冊deferred[ done | fail | progress ]
                        deferred[ tuple[1] ](function() {
                            var returned = fn && fn.apply( this, arguments );
                            //如果傳進來的回調函數會返回Deferred對象則在該對象上注冊事件
                            if ( returned && jQuery.isFunction( returned.promise ) ) {
                                returned.promise()
                                    .done( newDefer.resolve )
                                    .fail( newDefer.reject )
                                    .progress( newDefer.notify );
                            //否則對創建出來的newDefer執行對應事件
                            } else {
                                //如果上一個函數有返回值則接受傳返回值,否則傳上一個Deferred傳來的參數
                                newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
                            }
                        });
                    });
                    fns = null;
                }).promise();
            },
            // 如果deferred存在,將promise合並到deferred里,否則返回prmoise
            promise: function( obj ) {
                return obj != null ? jQuery.extend( obj, promise ) : promise;
            }
        },
        deferred = {};
    // 向后兼容
    promise.pipe = promise.then;

    // 對deferred添加剩余的方法
    jQuery.each( tuples, function( i, tuple ) {
        //取出對應列隊
        var list = tuple[ 2 ],
            //取出對應狀態
            stateString = tuple[ 3 ];
        // 賦予promise[ done | fail | progress ] = list.add
        promise[ tuple[1] ] = list.add;
        // 對狀態添加事件處理
        if ( stateString ) {
            list.add(function() {
                // 狀態state = [ resolved | rejected ]
                state = stateString;
            // 禁用對各列隊[ reject_list | resolve_list ].disable; progress_list.lock
            }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
        }

        // 分別注冊方法deferred[ resolve | reject | notify ]
        deferred[ tuple[0] ] = function() {
            deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
            return this;
        };
        // 注冊有with的方法
        deferred[ tuple[0] + "With" ] = list.fireWith;
    });

    // 將promise中的方法合並到deferred里
    promise.promise( deferred );

    // 如果jQuery.Deferred中的參數存在,則先用這個參數對deferred改造
    if ( func ) {
        func.call( deferred, deferred );
    }

    // 完成
    return deferred;
};

 

jQuery.when

jQuery.when是一個幫助Deferred隊列處理的工具,如果傳單一Deferred進去,則會返回其promise,如果傳多個Deferred進去,則會新建一個Deferred用以管理該Deferred隊列。

  • 如果隊列中有一個Deferred失敗,則整個隊列失敗。
  • 如果隊列中所有Deferred成功,則整個隊列成功。
  • 如果隊列中所有Deferred開始運行,則整個隊列正在運行。 
jQuery.when = function( subordinate /* , ..., subordinateN */ ) {
    var i = 0,
        //將arguments轉成數組
        resolveValues = core_slice.call( arguments ),
        //傳入Deferred對象總數
        length = resolveValues.length,

        // 未完成的Deferred總數
        remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

        // Deferred隊列管理器,如果參數只有一個Deferred則返回該Deferred
        deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

        // 更新resolve和progress的Deferred數量,全部處在這兩個狀態則通知管理器
        updateFunc = function( i, contexts, values ) {
            return function( value ) {
                contexts[ i ] = this;
                values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
                if( values === progressValues ) {
                    deferred.notifyWith( contexts, values );
                } else if ( !( --remaining ) ) {
                    deferred.resolveWith( contexts, values );
                }
            };
        },

        progressValues, progressContexts, resolveContexts;

    // 如果傳入Deferred總量大於1,則添加事件處理
    if ( length > 1 ) {
        progressValues = new Array( length );
        progressContexts = new Array( length );
        resolveContexts = new Array( length );
        for ( ; i < length; i++ ) {
            //判斷參數是不是可用的Deferred
            if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
                resolveValues[ i ].promise()
                    //單個成功則更新成功數
                    .done( updateFunc( i, resolveContexts, resolveValues ) )
                    //單個失敗則整個列隊失敗
                    .fail( deferred.reject )
                    //單個開始運行則更新運行中的個數
                    .progress( updateFunc( i, progressContexts, progressValues ) );
            //不可用則未完成數減1
            } else {
                --remaining;
            }
        }
    }

    // 如果沒有任何可用Deferred則直接通知管理器,列隊完成
    if ( !remaining ) {
        deferred.resolveWith( resolveContexts, resolveValues );
    }

    //返回Promise
    return deferred.promise();
};

其主要通過內置一個Deferred來管理隊列的運行狀態,不過其只將Promise暴露在外,而用閉包將所有Deferred保護起來。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM