jQuery-v2.0.3源码浅析04-Deferred

接下来我们来看下jQuery的延迟对象Deferred。
我们前面讲过Callbacks函数,其实Deferred就是在此基础进行了扩展,Deferred是对异步函数的统一管理。

源码

/**源码2999行**/
jQuery.extend({
    Deferred: function( func ) {
        var tuples = [
                // 类似Callbacks的fire, 类似Callbacks的add, Callbacks, 执行完毕的状态
                [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
                [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
                [ "notify", "progress", jQuery.Callbacks("memory") ]
            ],
            state = "pending",//没有执行之前状态默认为state
            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
                }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
            }

            // deferred[ resolve | reject | notify ]
            deferred[ tuple[0] ] = function() {
                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
        if ( func ) {
            func.call( deferred, deferred );
        }

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

根据代码我们能够解构出该函数主要的两个对象promise 和 deferred

promise解构

promise = {
  state,
  always,
  pipe = then,
  promise,
  done,
  fail,
  progress
}

deferred解构

deferred = {
  state,
  always,
  pipe = then,
  promise,
  done,
  fail,
  progress,
  resolve,
  reject,
  notify,
  resolveWith,
  rejectWith,
  notifyWith
}

相信看到这里很多理解了Callbacks的同学已经发现了,其实deferred 和 promise的区别其实就是,deferred对象提供了Callbacks的"fire"方法,而promise只有Callbacks的"add"。不过肯定很多同学肯定有跟我一样的疑惑,为什么要这样设计呢?其实也是比较好理解的,就拿ajax来说吧,打比方说我们ajax获取数据是否成功的状态能够被我们自己改变吗,答案是不能。所以deferred是提供给"内部方法"使用的,如果给"外部"使用我们就返回promise对象。例如:

function getDef(){
    var def = $.Deferred();
    //模拟延迟操作
    setTimeout(function(){
        def.resolve();
    }, 1000);
    return def.promise();
}
var def = getDef();
def.done(function(){
    console.log(1);
});

这个时候getDef()所返回的promise已经不包含'fire'方法,所以状态不会被随便串改。

假设我们上面getDef函数直接返回def,是不是会存在下面这种情况

function getDef(){
    var def = $.Deferred();
    //模拟延迟操作
    setTimeout(function(){
        def.resolve();
    }, 1000);
    return def;
}
var def = getDef();
def.done(function(){
    console.log(1);
});
def.resolve();
//发现程序一执行控制台就输出1了。

接下来我们来具体看一下源码是怎么实现的吧。

/**源码3046行**/
var tuples = [
  [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
  [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
  [ "notify", "progress", jQuery.Callbacks("memory") ]
]
/**源码3051行**/
jQuery.each( tuples, function( i, tuple ) {
    var list = tuple[ 2 ],
        stateString = tuple[ 3 ];

    // promise[ done | fail | progress ] = list.add
    /**源码3056行**/
    promise[ tuple[1] ] = list.add;

    // Handle state
    /**源码3059行**/
    if ( stateString ) {
        list.add(function() {
            // state = [ resolved | rejected ]
            state = stateString;

        // [ reject_list | resolve_list ].disable; progress_list.lock
        }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
    }

    // deferred[ resolve | reject | notify ]
    /**源码3069行**/
    deferred[ tuple[0] ] = function() {
        deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
        return this;
    };
    deferred[ tuple[0] + "With" ] = list.fireWith;
});

首先来看下tuples变量吧
1、resolve、reject、notify 等于 Callbacks 方法中提供的fire方法。源码3069行进行的赋值。
2、done、fail、progress 等于Callbacks 方法中提供的add方法。源码3056行进行的赋值。
3、源码3059行,会发现源码对 state = [ resolved | rejected ] 的list,add了方法,其实这段代码的意思是指 当执行玩resolve和reject,对state的状态进行修改,如果执行完resolve(成功)就不能再执行reject(失败)了。并对进行中的list进行了锁定操作。

接下来我们来看一下稍微复杂点的then方法,首先贴上源码

/**源码3017行**/
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
            /**源码3024行**/
            deferred[ tuple[1] ](function() {
                var returned = fn && fn.apply( this, arguments );
                if ( returned && jQuery.isFunction( returned.promise ) ) {
                    /**源码3071行**/
                    returned.promise()
                        .done( newDefer.resolve )
                        .fail( newDefer.reject )
                        .progress( newDefer.notify );
                } else {
                                        /**源码3076行**/
                    newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
                }
            });
        });
        fns = null;
    }).promise();
}

了解源码之前我们先来看看then有哪些写法,首先是第一种比较简单的写法,可以分别传3个回调函数分别对应3个状态的回调函数

function getDef(){
    var def = $.Deferred();
    //模拟延迟操作
    setTimeout(function(){
        def.resolve();
        //def.reject();
        //def.notify();
    }, 1000);
    return def;
}
var def = getDef();
def.then(function(){
    console.log('成功');
}, function(){
    console.log('失败');
}, function(){
    console.log('进行中');
});

该功能的实现代码是在源码的3024行实现的,将传入的三个函数分别add [done | fail | progress] 到对应的list中,最后直接通过var returned = fn && fn.apply( this, arguments );这句来执行回调函数。

then的第二种使用方法

function getDef(){
    var def = $.Deferred();
    //模拟延迟操作
    setTimeout(function(){
        def.resolve();
        // def.resolve();
        // def.notify();
    }, 1000);
    return def;
}
var def = getDef();
def.then(function(){
    console.log('成功');
    return '123';
}, function(){
    console.log('失败');
}, function(){
    console.log('进行中');
}).then(function(a){
    console.log(a);
}, function(){
    console.log('失败1');
}, function(){
    console.log('进行中1');
});

then每次执行完毕之后都会返回一个promise(包含then方法),所以一直通过then进行链接。然后通过源码3076行进行参数传递。如果return的是一个Deferred对象,则可以通过3071行实现链式写法。例如:

function getDef(){
    var def = $.Deferred();
    //模拟延迟操作
    setTimeout(function(){
        def.resolve();
        // def.resolve();
        // def.notify();
    }, 1000);
    return def.promise();
}
var def = getDef();
def.then(function(){
    console.log('成功');
    var def1 = getDef();
    return def1;
}).done(function(){
    console.log('成功1');
});

程序执行1秒之后打印成功,然后再过一秒打印出成功1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 突然就醒了, 在这个数九隆冬的凌晨2:30。 竟然记得醒前的梦, 不美不惊, 只是有一点点尴尬。 为曾经的年少不知...
    三得喵阅读 165评论 0 0