作者:禅楼望月(http://www.cnblogs.com/yaoyinglong )

1 引子

上一篇博文中介绍的Deferred,它表示一个延迟对象。但是很多时候,我们需要在多个延迟对象(异步代码)都执行完后再去执行另一段代码,这种情况下,使用Deferred就行不通了,就需要使用这里的$.when。

[+]view code
var wait1=$.Deferred(),
wait2=$.Deferred();

wait1.done(function () {
console.log('wait1-success');
});
wait2.done(function () {
console.log('wait2-success');
});

setTimeout(function () {
wait1.resolve();
},1000);
setTimeout(function () {
wait2.resolve();
},2000);

$.when(wait1,wait2)
.done(function () {
console.log('both wait1 and wait2 are success')
});

[+]view code
var wait1=$.Deferred(),
wait2=$.Deferred();

wait1.fail(function () {
console.log('wait1-fail');
});
wait2.fail(function () {
console.log('wait2-fail');
});

setTimeout(function () {
wait1.reject();
},1000);
setTimeout(function () {
wait2.reject();
},2000);

$.when(20,wait1,wait2)
.fail(function () {
console.log('fail');
});

可以看出,要触发done,必须当所有的Deferred都触发resolve,而要触发fail,只要任意一个Deferred触发reject即可。$.when的用途就是来管理多个延迟对象,其他只传一个Deferred或者传递的不是延迟对象。都是没什么意义的,并不代表会出错哦。

2 源码解析

在开始源码分析之前,我们先想一想,让我们自己实现这个$.when,应该怎么来实现。首先它肯定是个延迟对象,然后呢,它要等所有的Deferred都触发resolve它才触发resolve,所以需要一个计数器,计数器初始值为参数的个数,某个Deferred触发resolve时,我们就让计数器减1,某个Deferred触发reject时,我们就将计数器置零。然后调用相应的done或者fail,即可。那我们来看jQuery值怎么实现的。

2.1 对象构建

逐行分析:

remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0

当只有一个参数,判断它是不是Deferred,不是的话让计数器为0。其他情况计数器都等于参数的个数。这里忽略了其他参数不为Deferred的情况,而是将处理放在后面进行。

deferred = remaining === 1 ? subordinate : jQuery.Deferred()

如果目前检测出只有一个Deferred时(这种情况就是when的参数只有一个,并且为Deferred),when内部就是用这个Deferred。就如我上面所说的,这样做就没有任何意义了。只是让JS引擎多跑了几个弯而已。如果有多个Deferred,则创建一个新的Deferred。

接下来创建了一个名为updateFunc的函数,它是在执行期执行的,这里跳过。

[+]view code
if ( length > 1 ) {
progressValues = new Array( length );
progressContexts = new Array( length );
resolveContexts = new Array( length );
for ( ; i < length; i++ ) {
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
resolveValues[ i ].promise()
.done( updateFunc( i, resolveContexts, resolveValues ) )
.fail( deferred.reject )
.progress( updateFunc( i, progressContexts, progressValues ) );
} else {
--remaining;
}
}
}

if判断when有没有参数传递进来,其实这里没必要判断length,判断remaining就可以了。在if中,使用遍历when的所有参数。发现有不是Deferred的,立即--remaining。如果参数是Deferred,则为该Deferred的3个状态(resolve,reject,notify)分别注册函数。注册的这3个函数就是用来当传递进来的这些Deferred有相关动作的时候,让when的Deferred得到通知(--remaining或者触发done或者立即调用fail)。

if ( !remaining ) {
deferred.resolveWith( resolveContexts, resolveValues );
}

如果现在判断when的参数中没有Deferred是,直接触发when的resolveWith方法。因此就会触发done方法列表。

return deferred.promise();

返回一个精简版的Deferred。主要是为了防止在外部修改了when的Deferred的状态。

至此,when所对应的延迟对象构建成功。它是一个不能被修改状态的精简版Deferred。

2.2 执行期

执行期没什么内容,就是去执行upateFunc函数。

[+]view code
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
if( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) {
deferred.resolveWith( contexts, values );
}
};
}

在前面构建期,为参数Deferred添加注册时间时,done,progress是这样的:

done( updateFunc( i, resolveContexts, resolveValues ) )
progress( updateFunc( i, progressContexts, progressValues ) )

然后upateFunc返回一个闭包。闭包中,首先配置values,配置触发resolveWith触发时的参数,这个参数会传递给每一个订阅者。然后判断,如果是progress传递进来的方法,则为触发的是notifyWith。计数器,不做变化,只是调用通知订阅者列表。否则,则先让计数器减减,再判断计数器是否为0,是则触发resolveWith方法。这里的deferred指的是when的Deferred。不能混淆了。

那如果,when的参数中有一个触发了reject呢?就会直接调用deferred的reject方法。

fail( deferred.reject )

因为这里给when的每个Deferred参数的reject添加了一个这样的订阅方法:立即出发deferred的reject方法。

总体来说,when的源码还是比较简单的。

随机推荐

  1. Webstorm 2016内置web服务器配置

    运行three.js的官方的例子.本来想用IIS来运行,运行不了.所以用webstorm,用鼠标右键的方式,来运行,如下图 但是有一天,我把IIS配置好了,可以在IIS中运行了(只是把build文件夹 ...

  2. HTTP解析

    前言 B/S网络架构的核心是HTTP,掌握HTTP对一个从事互联网工作的程序员来说非常重要.要理解HTTP,最重要的是要熟悉HTTP的HTTP Header,HTTP Header控制着互联网上成千上 ...

  3. DNS拾遗(一)

    最近帮朋友注册域名配置主机,碰到一些DNS上的一些概念,惭愧于有一些东西已经忘记是啥意思,于是决定重新学习一下DNS方面的基本概念. 常用概念: TTL: TTL为Time to live的缩写,网络 ...

  4. 手把手教你用python打造网易公开课视频下载软件3-对抓取的数据进行处理

    上篇讲到抓取的数据保存到rawhtml变量中,然后通过编码最终保存到html变量当中,那么html变量还会有什么问题吗?当然会有了,例如可能html变量中的保存的抓取的页面源代码可能有些标签没有关闭标 ...

  5. SQL—大话函数依赖与范式

    说明:数据库中的某些概念真的很让人头疼,概念的东西本来就是很枯燥的,再加上枯燥的学习,那就更加枯燥了.概念这东西,你不理解也能生产东西,经验多了就行,但是为了更深入的学习,你还必须理解.这里,我抛开书 ...

  6. osgi 1

    Helloworld入门 准备: eclipse 3.4 需要jar,—— eclipse 自带的,plugin下面有很多,抛开里面的jar,很多都是当前项目不需要的,如果不适用eclipse而是直接 ...

  7. 已经为类型参数“Chart”指定了 constraint 子句。必须在单个 where 子句中指定类型参数的所有约束

    public abstract class FillWorkBook<TModel, Chart> where TModel : struct where Chart : new() wh ...

  8. iOS xcode6 设置多语言

    1,首先新建一个文件,选中ios模块下Rescource的Strings File 类型.eg:文件 2,选中该文件,右边栏选该文件属性,选中Localizable模块,选中localiz,这时会弹出 ...

  9. Java 9终于要包含Jigsaw项目了

    当Jigsaw在Java 9中最终发布时,这个项目的历史已经超过八年了. 转载于:http://www.itxuexiwang.com/a/liunxjishu/2016/0228/180.html? ...

  10. atitit 研发管理 要不要自己做引擎自己实现架构?.docx

    atitit 研发管理 要不要自己做引擎自己实现架构?.docx 1.1. 目前已经有很多引擎了,还要自己做吗??1 1.2. 答案是自己做更好,利大于弊1 2. 为什么要自己做??1 2.1. 从历 ...