學習jQuery.Deferred


關於Deferred對象,建議先看一下阮一峰老師的《jQuery的deferred對象詳解》

看完之后,相信你對Deferred已經有所了解,如果你還想繼續閱讀下面的源碼,可以先自己看下jQuery.Callbacks,或者先參考我前一篇文章:《學習jQuery.Callbacks》

完后再看下面的源碼注釋,相信你會發現,所謂的Deferred,其實就是構建了2條Callbacks的回調list,如下:

var calresolve = $.Callbacks('once memory'),
    calreject  = $.Callbacks('once memory'),
    calnotify  = $.Callbacks('memory');
            
var state = function(){
  state = stateString;
}
            
calresolve.add(state,calreject.disable,calnotify.lock);
calreject.add(state,calresolve.disable,calnotify.lock);

當我們var def = $.Deferred()時,就已經生成了上面的兩條list。我們還會發現,其實done/fail就是add,而resolve和reject就是fire。所以,當我們每次執行resolve或reject時,最少要執行3個回調,加上你新添加的回調(done||fail),分別是設置狀態值state和將另一種狀態禁用和鎖定。這樣就不會在你成功done時還能輸出fail。看下面代碼:

var fn1 = function(value){
  alert('fn1:' + value)
}
var def = $.Deferred();
def.done(fn1);              //相當於$.Callbacks('once memory').add(fn1)
def.resolve('littledu');    //相當於$.Callbacks('once memory').fire('littledu')

Deferred的原理也就大概如此。知道是怎么回事后,再重新看源碼,會發現一切都很清晰。其他的api就不介紹了,直接上源碼注釋吧,都是個人學習理解,如果有同學看到這里,請辨證的看,不包正確,被我誤導不負責任嘻嘻。

Deferred: function( func ) {
        //聲明一個二維數組,分別存放3個狀態的數組,需先了解jQuery.Callbacks。
        var tuples = [
                // action, add listener, listener list, final state
                [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
                [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
                [ "notify", "progress", jQuery.Callbacks("memory") ]
            ],
            state = "pending",   //回調狀態值,分別有pending,resolved和rejected
            promise = {
                state: function() {
                    return state;
                },
                always: function() {
                    deferred.done( arguments ).fail( arguments );
                    return this;
                },
                then: function( /* fnDone, fnFail, fnProgress */ ) {
                    var fns = arguments;
                    return jQuery.Deferred(function( newDefer ) {
                        jQuery.each( tuples, function( i, tuple ) {
                            var action = tuple[ 0 ],
                                fn = fns[ i ];
                            // deferred[ done | fail | progress ] for forwarding actions to newDefer
                            deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
                                function() {
                                    var returned = fn.apply( this, arguments );
                                    if ( returned && jQuery.isFunction( returned.promise ) ) {
                                        returned.promise()
                                            .done( newDefer.resolve )
                                            .fail( newDefer.reject )
                                            .progress( newDefer.notify );
                                    } else {
                                        newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
                                    }
                                } :
                                newDefer[ action ]
                            );
                        });
                        fns = null;
                    }).promise();
                },
                // Get a promise for this deferred
                // If obj is provided, the promise aspect is added to the object
                promise: function( obj ) {
                    return obj != null ? jQuery.extend( obj, promise ) : promise;
                }
            },
            deferred = {};  //聲明deferred對象
  
        // Keep pipe for back-compat
        promise.pipe = promise.then;
  
        // Add list-specific methods
        //
        jQuery.each( tuples, function( i, tuple ) {
            var list = tuple[ 2 ],  //保存Callbacks對象
                stateString = tuple[ 3 ];  //保存狀態值
  
            // promise[ done | fail | progress ] = list.add
            // 給promise添加方法,分別為done,fail,progress,綁定對應Callbacks的add方法,相當於調用deferred對象的done,fail,progress時,都是調用其Callbacks對象的add方法。
            // promise.done = list.add
            // promise.fail = list.add
            // promise.progress = list.add
            promise[ tuple[1] ] = list.add;
  
            // Handle state
            // action, add listener, listener list, final state
            //[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
            //[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
            //將狀態函數等扔進隊列
            if ( stateString ) { //stateString只有前兩個數組有值,為前兩個數組的Callbacks對象添加回調,均有一個存儲state的匿名函數和另外兩個Callbacks方法。其中第一個數組添加的是第二個數組的Callbacks對象的disable方法,第二個添加的是第一個數組的Callbacks對象的disable方法。lock方法則均為第3個數組的Callbacks對象所有
            //[state,reject.disable,notify.lock]
            //[state,resolve.disable,notify.lock]
            //var dfd = $.Deferred();
            //dfd.done(fn1) => [state,reject.disable,notify.lock,fn1]
            //dfd.state() => 在執行done的時候,第一個匿名函數就是設置state值的
                list.add(function() {
                    // state = [ resolved | rejected ]
                    state = stateString;
  
                // [ reject_list | resolve_list ].disable; progress_list.lock
                // 0^1=1 1^1=0
                }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
            }
  
            // deferred[ resolve | reject | notify ] = list.fire
            // 這里,相當於deferred對象調用resolve,reject,notify時,都執行回調隊列里的回調
            deferred[ tuple[0] ] = list.fire;
            deferred[ tuple[0] + "With" ] = list.fireWith;
        });
        // Make the deferred a promise
        // 將promise的方法推入deferred,此時promise有的deferred都有,deferred有的promise不一定有,如resolve和reject
        promise.promise( deferred );
  
        // Call given func if any
        // 如果jQuery.Deferred(fn)有傳入函數,則會執行這里,函數的context將會是deferred,參數也是deferred,在then中有用到,也可以外部直接傳入函數中使用
        if ( func ) {
            func.call( deferred, deferred );
        }
  
        // All done!
        return deferred;
    },
  
    // Deferred helper
    // when,可以理解為deferred的一個中轉站,比如我有一個函數,我要在這個函數執行完后再觸發回調,when就是做這樣的工作,它需要這個函數返回deferred對象,然后返回的是deferred.promise對象,從而可以實現$.when(fn).done(fnDone)的鏈式操作,因為done其實也就是Callbacks.add,所以這里相當於fnDone這個回調push進回調數組,然后在所傳的函數里面有一句resolve()或reject()執行一下回調數組,從而觸發回調。就是這樣。
    // PS:對於when我還有很多不明白,比如傳多個函數,有人說要兩個函數都resolve,才能執行done,但我測試回調的done或fail是以第一個函數為准的,也就是說如果它為resolve,那么回調會進入done,否則則進入fail,還有待觀察
    when: function( subordinate /* , ..., subordinateN */ ) {
        var i = 0,
            resolveValues = core_slice.call( arguments ),  //將deferred對象轉化為數組
            length = resolveValues.length,
  
            // the count of uncompleted subordinates
            // 如果length不等於1,則直接返回length,如果length==1,則要看所傳的函數是不是deferred對象,如果不是返回0,是的話返回length(1)
            remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
  
            // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
            // 如果remaining==1,則說明所傳函數是deferred對象,直接使用它,否則重新創建一個新的deferred對象
            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 ? core_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
        // 如果所傳函數不是deferred對象,則立即執行回調,這里用了resolveWidth,表明執行的都是done方法,resolveContexts為undefined,表明回調執行的上下文在window
        if ( !remaining ) {
            deferred.resolveWith( resolveContexts, resolveValues );
        }
  
        return deferred.promise();
    }

  


免責聲明!

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



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