jQuery源码分析,十五:when(方法的使用及源码实现分析

  • when方法的语法及使用
  • when源码实现分析

一、when方法的语法及使用

1.语法:

$.when( deferreds ) 

when本身相当于一个延迟回调对象集合的监听,当监听到所有回调对象都被触发了受理回调,它自身的一个延迟回调对象就会触发受理;反之,当它监听的回调对象中有一个触发了拒绝回调,它自身的延迟回调对象就会触发拒绝回调。

如果when方法不传入回调对象和任何实参,when默认为受理,直接触发它自身的受理回调。

所以,如果只单纯的看when自己的API语法是无法使用它的,应该结合$.Deferred.then方法一起使用,严格来说使用when方法时应该是按照以下模式来使用:

1 $.when( deferreds ) 
2     .then(doneFire,failFire);

关于when的使用就不太多赘述,毕竟使用起来比较简单,下面这个示例可以完整的了解when的使用:

 1 function conso(text){
 2     console.log(text);
 3 }
 4 $.when(
 5         a().then(conso,conso,conso),
 6         b().then(conso,conso,conso),
 7         c().then(conso,conso,conso)
 8     )
 9     .then(function(){
10         console.log("三个异步回调全部触发受理:resolve");
11     }, function(){
12         console.log("三个异步回调没有全部触发受理:reject");
13 });
14 
15 
16 function a(){
17     var df = $.Deferred();
18     setInterval(function(){
19         var score = Math.random() * 100;
20         if(score > 30){
21             df.resolve("a受理");
22         }else if(score < 20){
23             df.reject("a拒绝");
24         }else{
25             df.notify('a正在进行');
26         }
27     },1000);
28     return df.promise();
29 }
30 function b(){
31     var df = $.Deferred();
32     setInterval(function(){
33         var score = Math.random() * 100;
34         if(score > 30){
35             df.resolve("b受理");
36         }else if(score < 20){
37             df.reject("b拒绝");
38         }else{
39             df.notify('b正在进行');
40         }
41     },1000);
42     return df.promise();
43 }
44 function c(){
45     var df = $.Deferred();
46     setInterval(function(){
47         var score = Math.random() * 100;
48         if(score > 30){
49             df.resolve("c受理");
50         }else if(score < 20){
51             df.reject("c拒绝");
52         }else{
53             df.notify('c正在进行');
54         }
55     },1000);
56     return df.promise();
57 }

二、when源码实现分析

在了解when的源码之前必须先了解$.Callbacks和$.Deferred以及$.Deferred.then的源码实现,这三个内容我在前面两篇博客中详细的对这两个方法进行了解析以及源码模拟实现代码。连接:

jQuery使用():Callbacks回调函数列表之异步编程(含源码分析)

jQuery使用():Deferred有状态的回调列表(含源码)

相对于Callbacks和Deferred的实现,when的实现就要简单的多,实现方式非常类似Deferred.then的实现模式,Deferred.then是将后一个then节点的触发执行方法添加到前一个Deferred延迟回调列表上,when也是按照这样的实现方式实现的。

但是jQuery中的when实现还是有些疑问,当被监听的一个延迟回调对象受理是第一个触发,如果后面两个触发都是进行中的话,when自身的回调对象无法正确获取参数(undefined);如果第一个延迟回调对象进行中被触发,后面的进行中获得参数都是第一个延迟回调对象的参数。(带我解开这个疑惑以后来在补充模拟源码)

关于js的异步回调管理,我会在ES6的Promise中还会根据其API做详细分析,如果有时间也会试着模拟实现兼容API,在Promise中也同样存在着when方法,我尽量抽些时间模拟实现Promise的when方法作为补充。

源码我读了几遍,还是有些不理解为什么要写那么复杂,我自己写了一个简单的,功能目前我的理解来说与官方文档中所描述的没有差别:

 1 when:function(subordinate){
 2     let i = 0,
 3         resolveValues = [].slice.call(arguments), //将传入的Deferred对象复制一份添加到一个数组中
 4         length = resolveValues.length,
 5         remaining = length !== 1 || (subordinate && typeof subordinate.promise == 'function') ? length : 0, //记录未处理完的deferred个数
 6         deferred = jQuery.Deferred(),
 7         
 8         //更新deferred处理的进度
 9         updateResolve =  function(value){
10             if(remaining > 1){
11                 remaining --;
12             }else if(remaining === 1){
13                 deferred.resolveWith(this,value);
14             }
15         },
16         updataReject = function(value){
17             if(remaining){
18                 remaining = 0;
19                 deferred.rejectWith(this,value);
20             }
21         }
22         
23         if(length > 0){
24             for(;i < length; i++){
25                 resolveValues[i].done(updateResolve)
26                     .fail(updataReject);
27             }
28         }
29     return deferred.promise();    
30 }