zepto源碼研究 - deferred.js(jquery-deferred.js)

簡要:zepto的deferred.js 並不遵照promise/A+ 規範,而在jquery v3.0.0中的defer在必定程度上實現了promise/A+ ,所以本文主要研究jquery v3.0.0中的defer。html

首先   在上源碼前,本人以爲有必要認識一下promise/A+ 規範:https://segmentfault.com/a/1190000002452115python

接下來上源碼:jquery

define( [
    "./core",
    "./var/slice",
    "./callbacks"
], function( jQuery, slice ) {

"use strict";

function Identity( v ) {
    return v;
}
function Thrower( ex ) {
    throw ex;
}

function adoptValue( value, resolve, reject ) {
    var method;

    try {

        // Check for promise aspect first to privilege synchronous behavior
        if ( value && jQuery.isFunction( ( method = value.promise ) ) ) {
            method.call( value ).done( resolve ).fail( reject );

        // Other thenables
        } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) {
            method.call( value, resolve, reject );

        // Other non-thenables
        } else {

            // Support: Android 4.0 only
            // Strict mode functions invoked without .call/.apply get global-object context
            // 假設value是常量,resolve會馬上調用,而且傳入參數爲value
            resolve.call( undefined, value );
        }

    // For Promises/A+, convert exceptions into rejections
    // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
    // Deferred#then to conditionally suppress rejection.
    } catch ( value ) {

        // Support: Android 4.0 only
        // Strict mode functions invoked without .call/.apply get global-object context
        // 這裏執行master.reject(value)
        reject.call( undefined, value );
    }
}

jQuery.extend( {

    Deferred: function( func ) {
        //元組:描述狀態、狀態切換方法名、對應狀態執行方法名、回調列表的關係
        //tuple引自C++/python,和list的區別是,它不可改變 ,用來存儲常量集
        var tuples = [

                // action, add listener, callbacks,
                // ... .then handlers, argument index, [final state]
                [ "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" ]
            ],
            state = "pending",  //Promise初始狀態
        //promise對象,promise和deferred的區別是:
        /*promise只包含執行階段的方法always(),then(),done(),fail(),progress()及輔助方法state()、promise()等。
         deferred則在繼承promise的基礎上,增長切換狀態的方法,resolve()/resolveWith(),reject()/rejectWith(),notify()/notifyWith()*/
        //因此稱promise是deferred的只讀副本
            promise = {
                /**
                 * 返回狀態
                 * @returns {string}
                 */
                state: function() {
                    return state;
                },
                /**
                 * 成功/失敗狀態的 回調調用
                 * @returns {*}
                 */
                always: function() {
                    deferred.done( arguments ).fail( arguments );
                    return this;
                },
                // TODO 待解釋
                "catch": function( fn ) {
                    return promise.then( null, fn );
                },
                /**
                 *
                 * @returns promise對象
                 */
                // Keep pipe for back-compat
                pipe: function( /* fnDone, fnFail, fnProgress */ ) {
                    var fns = arguments;
                    //注意,這不管如何都會返回一個新的Deferred只讀副本,
                    //因此正常爲一個deferred添加成功,失敗,千萬不要用pipe,用done,fail
                    return jQuery.Deferred( function( newDefer ) {
                        jQuery.each( tuples, function( i, tuple ) {

                            // Map tuples (progress, done, fail) to arguments (done, fail, progress)
                            // 根據tuple,從fns裏取出對應的fn
                            var fn = jQuery.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 })
                            //註冊fn的包裝函數
                            deferred[ tuple[ 1 ] ]( function() {
                                //直接執行新添加的回調 fnDone fnFailed fnProgress
                                var returned = fn && fn.apply( this, arguments );
                                //返回結果是promise對象
                                if ( returned && jQuery.isFunction( returned.promise ) ) {
                                    //轉向fnDone fnFailed fnProgress返回的promise對象
                                    //注意,這裏是兩個promise對象的數據交流
                                    //新deferrred對象切換爲對應的成功/失敗/通知狀態,傳遞的參數爲 returned.promise() 給予的參數值
                                    returned.promise()
                                        .progress( newDefer.notify )
                                        .done( newDefer.resolve )
                                        .fail( newDefer.reject );
                                } else {
                                    //新deferrred對象切換爲對應的成功/失敗/通知狀態
                                    newDefer[ tuple[ 0 ] + "With" ](
                                        this,
                                        fn ? [ returned ] : arguments
                                    );
                                }
                            } );
                        } );
                        fns = null;
                    } ).promise();
                },
                then: function( onFulfilled, onRejected, onProgress ) {
                    var maxDepth = 0;
                    //depth == 0;
                    //special == newDefer.notifyWith,用來判斷是否爲pending狀態觸發
                    //deferred == jQuery.Deferred() 返回的新的defer對象即then()返回的對象即newDefer
                    //handler == [onFulfilled,onRejected,onProgress]||Identity,Identity爲簡單返回形參的fn,
                    //在resolve返回的fn中handle以deferred爲上下文執行
                    function resolve( depth, deferred, handler, special ) {
                        //這個fn會在then的調用者對應的回調列表中,是handler的包裝函數,在此稱之爲wrappeHandler;

                        return function() {
                            //這個this是不定的,好比 fn.call(obj,[args])
                            var that = this,
                                args = arguments,
                                //TODO 這是什麼功能?
                                mightThrow = function() {

                                    var returned, then;

                                    // Support: Promises/A+ section 2.3.3.3.3
                                    // https://promisesaplus.com/#point-59
                                    // Ignore double-resolution attempts
                                    // 通常是0和0,1和1,有種狀況是0和1
                                    // defer.then(sfn,rfn,nfn);nfn先執行並返回promise,該promise會then(fn1),
                                    //    注:此fn1是由resolve(depth = 0)生成,所以fn1內部執行時depth == 0
                                    // 接下來執行sfn,此時返回promise1,maxDepth++,
                                    // 若是接下來promise被resolve了,便會執行fn1,便會出現上述狀況
                                    if ( depth < maxDepth ) {
                                         return;
                                    }
                                    //傳入的handler以當前上下文和參數執行
                                    returned = handler.apply( that, args );

                                    // Support: Promises/A+ section 2.3.1
                                    // https://promisesaplus.com/#point-48
                                    // newPromise1 = defer.then(function(){return newPromise});
                                    // newPromise在resolve時執行回調函數fn1,而fn1執行newPromise1的回調函數,
                                    // 若newPromise1 === newPromise,則會出現死循環
                                    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
                                    // 若是有returned.then,則returned爲promise
                                    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 ( jQuery.isFunction( then ) ) {

                                        // Special processors (notify) just wait for resolution
                                        // special可判斷此wrappeHandler在pending列表中仍是在resolver或reject中
                                        if ( special ) {
                                            then.call(
                                                returned,
                                                resolve( maxDepth, deferred, Identity, special ),
                                                resolve( maxDepth, deferred, Thrower, special )
                                            );

                                        // Normal processors (resolve) also hook into progress
                                        } else {
                                            //若是這個包裝函數wrappeHandler是在resolve列表或reject列表中
                                            //newDefer = defer.then(function(){return promise});
                                            //defer在resolve時候執行function(){return promise}的包裝函數,在此包裝函數中則會執行到此
                                            //即要執行到此,則必須知足 1:此包裝函數對應的defer  resolve and  reject,2:hander 返回promise 
                                            // ...and disregard older resolution values
                                            // notify裏面返回的promise在resolve後的參數可能會傳遞給第2個then去執行
                                            // 若是在這以前resolve已經將參數傳遞給了第2個then,這裏要防止老數據
                                            maxDepth++;
                                            /*
                                            * returned.then(resolve( maxDepth, deferred, Identity, special ))
                                            * deferred:下文中的newDefer,做用是Identity()執行後,newDefer.resolveWidth
                                            * */
                                            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)
                                        /*
                                        * newDefer = defer.then(fn1).then(fn2);
                                        * defer註冊fn1,將封裝了fn1的包裝函數bfn1加入到defer的回調列表中,newDefer註冊fn2,同理有bfn2
                                        * 這裏是 handler 返回的是普通對象,則newDefer當即resolve的即當即執行fn2
                                        * 若是走defer走resolve流程時,此時fn1 === handler的則newDefer.resolve(fn1的that,fn1的args);
                                        * 若是fn1返回的returned 走resolve流程,此時handler === identity,則newDefer.resolve(undefined,identity的return);
                                        * */
                                        if ( handler !== Identity ) {
                                            that = undefined;
                                            args = [ returned ];
                                        }

                                        // Process the value(s)
                                        // Default process is resolve
                                        // 不管then裏面的函數返回的promise是notify,resolve,reject,最終都會執行resolveWith
                                        ( special || deferred.resolveWith )( that, args );
                                    }
                                },

                                // Only normal processors (resolve) catch and reject exceptions
                                // 這裏是對異常的處理??
                                process = special ?
                                    mightThrow :
                                    function() {
                                        try {
                                            mightThrow();
                                        } catch ( e ) {

                                            /*jQuery.Deferred.exceptionHook = function( error, stack ) {

                                             // Support: IE 8 - 9 only
                                             // Console exists when dev tools are open, which can happen at any time
                                             if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
                                             window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
                                             }
                                             };*/

                                            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
                                            // 正常的註冊結構出現異常會走下面流程
                                            // TODO 暫不清楚 depth + 1 < maxDepth 的狀況
                                            if ( depth + 1 >= maxDepth ) {

                                                // Only substitute handlers pass on context
                                                // and multiple values (non-spec behavior)
                                                if ( handler !== Thrower ) {
                                                    that = undefined;
                                                    args = [ e ];
                                                }
                                                //正常狀況下第一個then的resolve和reject出現異常,會致使第二個then裏面的reject執行
                                                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
                                // TODO 不太明白
                                if ( jQuery.Deferred.getStackHook ) {
                                    process.stackTrace = jQuery.Deferred.getStackHook();
                                }
                                window.setTimeout( process );
                            }
                        };
                    }
                    /*
                    * defer.then(fn)    -->
                    * 建立newDefer        -->
                    * defer關聯的回調列表(下文中的tuples[ * ][ 3 ])增長一個fn的包裝函數(由resolve生成),
                    * 這個包裝函數執行fn,並對其返回值和newDefer作出相應處理    -->
                    * 返回newDefer
                    * */
                    return jQuery.Deferred( function( newDefer ) {

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

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

                        // rejected_handlers.add( ... )
                        tuples[ 2 ][ 3 ].add(
                            resolve(
                                0,
                                newDefer,
                                jQuery.isFunction( onRejected ) ?
                                    onRejected :
                                    Thrower
                            )
                        );
                    } ).promise();    //返回defer的只讀版本promise
                },

                // Get a promise for this deferred
                // If obj is provided, the promise aspect is added to the object
                /**
                 * 返回obj的promise對象
                 * @param obj
                 * @returns {*}
                 */
                promise: function( obj ) {
                    return obj != null ? jQuery.extend( obj, promise ) : promise;
                }
            },

            //內部封裝deferred對象
            deferred = {};

        // Add list-specific methods
        //給deferred添加切換狀態方法
        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的done、fail、progress爲Callback的add方法,使其成爲回調列表
            //簡單寫法:  promise['done'] = jQuery.Callbacks( "once memory" ).add
            // promise['fail'] = jQuery.Callbacks( "once memory" ).add  promise['progress'] = jQuery.Callbacks( "memory" ).add
            promise[ tuple[ 1 ] ] = list.add;

            // Handle state
            //切換的狀態是resolve成功/reject失敗
            //添加首組方法作預處理,修改state的值,使成功或失敗互斥,鎖定progress回調列表,
            if ( stateString ) {
                /*
                if (stateString) {
                 list.add(function(){
                 state = stateString

                 //i^1  ^異或運算符  0^1=1 1^1=0,成功或失敗回調互斥,調用一方,禁用另外一方
                 }, tuples[i^1][2].disable, tuples[2][2].lock)
                 }
                 */
                list.add(
                    function() {

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

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

                    // progress_callbacks.lock
                    tuples[ 0 ][ 2 ].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(...) }
            //添加切換狀態方法 resolve()/resolveWith(),reject()/rejectWith(),notify()/notifyWith()
            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
        //deferred繼承promise的執行方法
        promise.promise( deferred );

        // Call given func if any
        //傳遞了參數func,執行
        if ( func ) {
            func.call( deferred, deferred );
        }

        // All done!
        //返回deferred對象
        return deferred;
    },

    // Deferred helper
    /**
     *
     * 主要用於多異步隊列處理。
     多異步隊列都成功,執行成功方法,一個失敗,執行失敗方法
     也能夠傳非異步隊列對象

     * @param sub
     * @returns {*}
     */
    when: function( singleValue ) {
        var

            // count of uncompleted subordinates
            remaining = arguments.length,

            // count of unprocessed arguments
            i = remaining,

            // subordinate fulfillment data
            resolveContexts = Array( i ),
            resolveValues = slice.call( arguments ),  //隊列數組 ,未傳參數是[],slice能將對象轉化爲數組

            // the master Deferred
            // 這是主分支 .when().then(),master決定.then()的執行
            master = jQuery.Deferred(),

            // subordinate callback factory
            // .when()參數中每個value被resolve後調用下面的返回函數
            // 1:將每個調用者和調用參數存在數組裏,2: 最後以數組做爲參數,由master.resolve
            updateFunc = function( i ) {
                return function( value ) {
                    resolveContexts[ i ] = this;
                    // updateFunc()(v1,v2,v3),resolveValues[ i ] = [v1,v2,v3],若只有一個參數,
                    // 則resolveValues[ i ] = value
                    resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
                    if ( !( --remaining ) ) {
                        //若是這是最後一個resolveValues被解決
                        master.resolveWith( resolveContexts, resolveValues );
                    }
                };
            };

        // Single- and empty arguments are adopted like Promise.resolve
        if ( remaining <= 1 ) {
            // 將第二個和第三個參數註冊到第一個參數裏面去
            // 若是singleValue是常量,則馬上執行master.resolve,下面的判斷不會執行
            adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject );

            // Use .then() to unwrap secondary thenables (cf. gh-3000)
            //
            if ( master.state() === "pending" ||
                jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {

                return master.then();
            }
        }

        // Multiple arguments are aggregated like Promise.all array elements
        // 循環爲resolveValues[i] 註冊updateFunc()方法 --> 判斷計數到最後一個則執行 master.resolve
        // resolveValues[i].reject-->list.add(master.reject);
        while ( i-- ) {
            //    當resolveValues[ i ]爲常量時,會馬上執行updateFunc( i ),
            // 若是全部的都爲常量,則 執行master.resolve(resolveValues)
            adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
        }

        return master.promise();
    }
} );

return jQuery;
} );

以上內容主要是針對then,pipe,when 作了更新與修改,segmentfault

pipe:jquery.Deferred(fn)裏面會先新建一個newDefer,而後傳入newDefer做爲參數並執行fn,fn會將pipe裏的參數依次加入到回調列表中,而且判斷參數返回值是否爲promise,若果是,則將newDefer的狀態轉化器注入到promise的回調列表中,不然,直接讓newDefer發生相應的狀態轉化。數組

測試代碼以下:promise

var defer = $.Deferred();
        defer.pipe(function(data){
            console.log(data);
            var defer1 = $.Deferred();
            setTimeout(function () {
                defer1.resolve("second pipe");
            },1000);
            return defer1
        }).pipe(function (data) {
            console.log(data);
        });
        defer.resolve("first pipe");

結果:
first pipe
second pipe

then:主要流程以下:app

function then(fnDone,fnFail,fnPro){
            var maxDepth = 0;       //聲明調用深度,這裏的鏈式調用實爲遞歸調用
            function resolve()....  //resolve封裝了then的參數

            var defer = jQuery.Deferred(function (newDefer) {
                Tuples.add(resolve(fnDone),resolve(fnFail),resolve(fnPro));
            })
        }

這裏主要流程在resolve函數裏面,resolve返回一個封裝函數fn,這個fn的主要功能是管理和執行resovle中的handle參數,mightThrow 方法主要實現基本的promise功能異步

若是fnDone和fnFail出現了異常,則捕獲異常,並newDefer.reject(); async

以下例子:ide

var defer = $.Deferred();
            defer.then(function () {
                throw new Error("resolve error");
            }).then(function () {
                console.log("second then resolve");
            },function (data) {
                console.log("second then reject");
                console.log(data);
            });
            defer.resolve();

結果:
second then reject
 Error: resolve error
    at file:///defer.html:10:19
    at jQuery.extend.Deferred.promise.then.mightThrow (file:///jquery-3.0.0.js:3507:29)
    at jQuery.extend.Deferred.promise.then.process (file:///jquery-3.0.0.js:3576:12)

mightThrow方法 首先執行 returned = handler.apply(that,args);  若是returned的類型是promise,則將newDefer的狀態轉換交給returned的回調列表進行管理,若爲常量,則直接調用newDefer.resolve(); 從pending狀態到resolve狀態其中的流程大可能是不可控的,爲避免pending的數據影響resolve ,因而用depth來區分。

defer.when: 傳一個或一組參數,能夠是常量也能夠是promise,這裏有兩個地方我以爲是很是好的,第一個是把when中每一個參數完成後的計數操做提取出來造成一個函數

updateFunc(i),第二個是adoptValue ,將master的狀態變化注入到每一個參數的回調列表中由其統一管理。

以下例子:

$.when("no-promise").then(function (data) {
            console.log(data + "Execution without delay!");
});
結果:
no-promiseExecution without delay!

var defer1 = $.Deferred();
        var defer2 = $.Deferred();
        var defer3 = $.Deferred();

        $.when(defer1,defer2,defer3).then(function (d1,d2,d3) {
            console.log(d1);
            console.log(d2);
            console.log(d3);
        });

        setTimeout(function () {
            defer1.resolve("defer1");
        },1000);
        setTimeout(function () {
            defer2.resolve("defer2");
        },2000);
        setTimeout(function () {
            defer3.resolve("defer3");
        },3000);

結果:
defer1
defer2
defer3

注:本人小菜一枚,如有不通之處,敬請指教

相關文章
相關標籤/搜索