天天看點

jQuery回調、遞延對象總結(中篇) —— 神奇的then方法

前言:

什麼叫做遞延對象,生成一個遞延對象隻需調用jQuery.Deferred函數,deferred這個單詞譯為延期,推遲,即延遲的意思,那麼在jQuery中

又是如何表達延遲的呢,從遞延對象中的then方法或許能找到這種延遲的行為,本文重點解讀遞延對象中的then方法

jQuery回調、遞延對象總結篇索引:

jQuery回調、遞延對象總結(上篇)—— jQuery.Callbacks

jQuery回調、遞延對象總結(中篇) —— 神奇的then方法

jQuery回調、遞延對象總結(下篇) —— 解密jQuery.when方法

設計思路:

在遞延對象構造中,分别有三組回調對象,每一組回調對象都有與之對應的行為(action,add listener),和狀态(final state),

這些行為都歸納為遞延對象中的(觸發回調,添加函數到回調清單中等)方法

jQuery.Deferred構造源碼

Deferred構造源碼除了then函數源碼外,其他都非常簡單,這裡不做過多解讀,後面将重點讨論then方法

jQuery回調、遞延對象總結(中篇) —— 神奇的then方法
jQuery回調、遞延對象總結(中篇) —— 神奇的then方法
jQuery.extend({

    Deferred: function( func ) {
        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",
            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 = jQuery.isFunction( fns[ i ] ) && fns[ i ];
                            // deferred[ done | fail | progress ] for forwarding actions to newDefer
                            deferred[ tuple[1] ](function() {
                                var returned = fn && 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 === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
                                }
                            });
                        });
                        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 = {};

        // Keep pipe for back-compat
        promise.pipe = promise.then;

        // Add list-specific methods
        jQuery.each( tuples, function( i, tuple ) {
            var list = tuple[ 2 ],
                stateString = tuple[ 3 ];

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

            // Handle state
            if ( stateString ) {
                list.add(function() {
                    // state = [ resolved | rejected ]
                    state = stateString;

                // [ reject_list | resolve_list ].disable; progress_list.lock
                // 可以看看上篇中lock方法的各種場景調用
                }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
            }

            // deferred[ resolve | reject | notify ]
            deferred[ tuple[0] ] = function() {
                // 如果方法不被借用,那麼回調中的this對象為promise,沒有觸發回調的方法
                deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
                return this;
            };
            deferred[ tuple[0] + "With" ] = list.fireWith;
        });

        // Make the deferred a promise
        promise.promise( deferred );

        // Call given func if any
        // 作用于then方法
        if ( func ) {
            func.call( deferred, deferred );
        }

        // All done!
        return deferred;
    }
});      

View Code

神奇的then方法

在實際項目應用中,一個頁面或許有多個ajax請求,你可能會這樣做:

$.ajax({ url1, ... });
$.ajax({ url2, ... });
$.ajax({ url3, ... });
...      

這樣做的缺點:

1、多個ajax同時發送請求,可能會造成伺服器壓力,對于富應用頁面來說,如果請求過多,那是必然的;

2、對于頁面底部,或者說首屏不展示給使用者浏覽的部分需要發送的ajax請求,沒有必要讓它一開始加載頁面後就發送請求,這樣會造成頁面響應緩慢

jQuery遞延對象中的then方法好像天生就是為了解決以上問題而設計的,它可以按照順序依次處理多個異步請求,即第一個請求處理完後,

再處理第二個請求,以此類推,這樣既可以減輕伺服器壓力,又可以先發送首屏(從上到下)頁面部分的請求,使頁面響應更快

來看看一段非常優雅的執行個體代碼

var promiseA = $.get(urlA);
promiseA.always(doneFnA, failFnA, progressFnA);

var promiseB = promiseA.then(function(){
    return $.get(urlB);
});
promiseB.always(doneFnB, failFnB, progressFnB);      

或者你也可以這樣寫,但并不建議:

jQuery回調、遞延對象總結(中篇) —— 神奇的then方法
jQuery回調、遞延對象總結(中篇) —— 神奇的then方法
var promiseB = $.get(urlA).then(function(){
    var state = this.state();
    // 針對第一個ajax請求的處理
    switch (state) {
        case 'resolved' :
            doneFnA();
            break;
        case 'rejected' :
            failFnA();
            break;
        case 'pending' :
            progressA();
            break;
        default: 
            break;
    }
    return $.get(urlB);
});
promiseB.always(doneFnB, failFnB, progressB);      

View Code

上面代碼是如何運作的呢:

首先發送第一個ajax請求,當promiseA對象執行過resolve(或reject、notify)後,即:第一個請求成功或失敗後,将依次執行回調doneFnA

(或failFnA、progressFnA),then中的匿名函數(注意代碼的順序,之前代碼順序有誤,把promiseA.always放在了then方法執行之後,現已改過來了),

匿名函數中發送第二個ajax請求,當請求成功或失敗後,将執行對應的回調函數(doneFnB或failFnB、progressFnB)

衍生後的代碼

var promiseA = $.get(urlA);
// 這裡添加promiseA的回調

var promiseB = promiseA.then(function(){
    return $.get(urlB);
});
// 這裡添加promiseB的回調

var promiseC = promiseB.then(function(){
    return $.get(urlC);
});
// 這裡添加promiseC的回調

var promiseD = promiseC.then(function(){
    return $.get(urlD);
});
// 這裡添加promiseD的回調      

再來看看then函數中的構造源碼,通過上面的執行個體分析,相信眼前的你會恍然大悟的

promise = {
    then: function( /* fnDone, fnFail, fnProgress */ ) {
        var fns = arguments;
        // 傳回後的promise對象與newDefer對應
        return jQuery.Deferred(function( newDefer ) {
            jQuery.each( tuples, function( i, tuple ) {
                var action = tuple[ 0 ],
                    fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
                // deferred[ done | fail | progress ] for forwarding actions to newDefer
                // 為第一個遞延對象添加回調
                deferred[ tuple[1] ](function() {
                    var returned = fn && fn.apply( this, arguments );

                    // 如果回調傳回的是一個遞延對象,newDefer将根據這個傳回的遞延對象的狀态來觸發行為
                    if ( returned && jQuery.isFunction( returned.promise ) ) {   
                        
                        returned.promise()
                            .done( newDefer.resolve )
                            .fail( newDefer.reject )
                            .progress( newDefer.notify );
                    }
                    // 如果回調傳回的不是一個遞延對象,newDefer将根據第一個(deferred)遞延對象的狀态來觸發行為
                    else {
                        newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
                    }
                });
            });
            fns = null;
        }).promise();
    }
}      

PS: 如有描述錯誤,請幫忙指正,如果你們有不明白的地方也可以發郵件給我,

  如需轉載,請附上本文位址及出處:部落格園華子yjh,謝謝!