zepto源码研究 - deferred.js,jquery-deferred.js

简要:zepto的deferred.js 并不遵守promise/A+ 规范,而在jquery v3.0.0中的defer在一定程度上实现了promise/A+ ,因此本文主要研究jquery v3.0.0中的defer。

首先 在上源码前,本人觉得有必要认识一下promise/A+ 规范:https://segmentfault.com/a/1190000002452115

接下来上源码:

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 做了更新与修改,

pipe:jquery.Deferred(fn)里面会先新建一个newDefer,然后传入newDefer作为参数并执行fn,fn会将pipe里的参数依次加入到回调列表中,并且判断参数返回值是否为promise,若果是,则将newDefer的状态转化器注入到promise的回调列表中,否则,直接让newDefer发生相应的状态转化。

测试代码如下:

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:主要流程如下:

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();

如下例子:

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

注:本人小菜一枚,若有不通之处,敬请指教