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

前言:

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

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

 

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

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

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

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

 

設計思路:

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

這些行爲都概括爲遞延對象中的(觸發回調,添加函數到回調列表中等)方法函數

 

jQuery.Deferred構造源碼

Deferred構造源碼除了then函數源碼外,其餘都很是簡單,這裏不作過多解讀,後面將重點討論then方法this

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請求,你可能會這樣作:url

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

這樣作的缺點:

一、多個ajax同時發送請求,可能會形成服務器壓力,對於富應用頁面來講,若是請求過多,那是必然的;

二、對於頁面底部,或者說首屏不展現給用戶瀏覽的部分須要發送的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);

或者你也能夠這樣寫,但並不建議:

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,謝謝!

相關文章
相關標籤/搜索