首页 > 代码库 > 一步一步实现基于Task的Promise库(二)all和any方法的设计和实现

一步一步实现基于Task的Promise库(二)all和any方法的设计和实现

在上一篇中我们已经初步完成了Task类,如果仅仅是这些,那么没有多大意义,因为网上这类js库有很多,现在我们来些更复杂的使用场景。

如果我们现在有这样一个需求:我们要先读取aa.txt的内容,然后去后台解析,同时bb.txt也要读取解析,然后当两个文件都解析完了,我们还要合并两部分内容存到cc.txt中,最后发个通知说ok了。。需求很变态,但是我还是想问有没有好的办法呢?按照最原始的嵌套回调的写法好像不是那么容易了,因为你没法知道aa.txt和bb.txt两个文件的读取解析谁先完成,所以你除了要关注逻辑本身还得花费一些功夫在这个谁先谁后上面,如果需求变了。。。然后就没有然后了,想想我就要吐血。

那么回到上一章的Task类,Task有没有办法解决这个问题呢?回答是有,先看下面的代码:

1 var taskExp_1 = new Task(readFile, ["aa.txt"]).then(resolveFile, ["/service/fileResolve.ashx?file=aa.txt"]);2 var taskExp_2 = new Task(readFile, ["bb.txt"]).then(resolveFile, ["/service/fileResolve.ashx?file=bb.txt"]);3 var taskExp_3 = new Task(taskExp_1, taskExp_2).all(writeFile, ["cc.txt"]).then(sendMail).start();

如同需求描述的一样,taskExp_1,taskExp_2分别描述了aa.txt和bb.txt的读取和解析,taskExp_3的构造方法里面包括了taskExp_1,taskExp_2,后面的all方法表示需要taskExp_1和taskExp_2都完成才能执行writeFile,最后才是sendMail。这样一来彻底解决了这类需求带来的复杂性,如果把all改成any表示taskExp_1和taskExp_2完成其中一个就能执行writeFile。

在实现all和any方法之前,先看一看我们将要面临哪些问题:

  1. 现在new Task(),then(),all(),any()方法的形参可以是多个异步操作了,并且每个异步操作可以是一个方法和它的参数,也可以是一个Task。
  2. 既然这些方法的形参可以是Task,那么我们理所当然要支持Task的完成通知。
  3. 一组异步操作的返回值如何传递到下一组异步操作 ,对于all方法如何接收前一组异步操作的所有输出参数呢?在上一章里我们通过this.param接收参数,这里对于then,any方法同样如此,因为最终它们只会有一个异步操作的输出参数传递到下一个异步操作,all方法比较特殊,我斟酌好久决定使用this.param[0],this.param[1]... 来接收同组的不同异步操作的输出参数
  4. 当不同状态的Task作为参数传递到另一个Task中时(未开始执行的,正在执行的,执行完成的),应该怎么处理?我们还得支持Task的状态管理。

继续上一章所写的Task类来扩展这两个方法,现在我们以一个标准的js库形式来书写实现细节,这个库命名为Task.js:

  1 (function(){  2     var isFunction = function(target){  3         return target instanceof Function;  4     };  5     var isArray = function(target){  6         return target instanceof Array;  7     };  8   9     //自定义事件管理(代码摘抄自http://www.cnblogs.com/dolphinX/p/3254017.html) 10     var EventManager = function(){ 11         this.handlers = {}; 12     }; 13     EventManager.prototype = { 14         constructor: EventManager, 15         addHandler: function(type, handler){ 16             if(typeof this.handlers[type] == ‘undefined‘){ 17                 this.handlers[type] = new Array(); 18             } 19             this.handlers[type].push(handler); 20         }, 21         removeHandler: function(type, handler){ 22             if(this.handlers[type] instanceof Array){ 23                 var handlers = this.handlers[type]; 24                 for(var i=0; i<handlers.length; i++){ 25                     if(handler[i] == handler){ 26                         handlers.splice(i, 1); 27                         break; 28                     } 29                 } 30             } 31         }, 32         trigger: function(type, event){ 33             /* 34             if(!event.target){ 35                 event.target = this; 36             } 37             */ 38             if(this.handlers[type] instanceof Array){ 39                 var handlers = this.handlers[type]; 40                 for(var i=0; i<handlers.length; i++){ 41                     handlers[i](event); 42                 } 43             } 44         } 45     }; 46  47     //WorkItem的参数和Task一样,有下面几种形式,不过最终存到subItems里面的元素只有两种类型.一种是方法和它的参数,一种是Task对象 48     //1.(func, [funcArg1, funcArg2]) 49     //2.(func, funcArg1, funcArg2, ....) 50     //3.(task1, task2, ....) 51     //4.([func, funcArg1, funcArg2, ....] 52     //   ,[func, [funcArg1, funcArg2]] 53     //   ,task1 54     //   , .... 55     //  ) 56     var WorkItem = function(arrayArgs){ 57         //WorkItem其实是一组异步操作的集合,_subItems就是这个集合 58         var _subItems = []; 59         var _checkFunc = function(args){ 60             if(isFunction(args[0])){ 61                 if(args.length == 2 && isArray(args[1])){ 62                     _subItems.push({‘isFunc‘: true, ‘func‘: args[0], ‘args‘: args[1]}); 63                 } 64                 else{ 65                     _subItems.push({‘isFunc‘: true, ‘func‘: args[0], ‘args‘: args.slice(1)}); 66                 } 67                 return true; 68             } 69             return false; 70         }; 71         var _checkTask = function(task){ 72             if(task instanceof Task){ 73                 _subItems.push({‘isFunc‘: false, ‘task‘: task}); 74             } 75         }; 76         //这里是对形参的检测,看看是否满足上面的4种形式 77         if(!_checkFunc(arrayArgs)){ 78             for(var i=0; i<arrayArgs.length; i++){ 79                 if(!_checkFunc(arrayArgs[i])){ 80                     _checkTask(arrayArgs[i]); 81                 } 82             } 83         } 84  85         //开始执行SubItem 86         var _startSubItem = function(subItemIndex, context){ 87             var subItem = _subItems[subItemIndex]; 88             //如果subItem是方法和它的参数 89             if(subItem.isFunc){ 90                 //先获取方法的上下文环境(就是方法里面的this) 91                 var workItemCxt = context.getWorkItemContext(subItemIndex); 92                 //再执行方法 93                 subItem.func.apply(workItemCxt, subItem.args); 94             } 95             //如果subItem是Task对象 96             else{ 97                 //如果task已经完成 98                 if(subItem.task.getStatus() == TaskStatus.finished){ 99                     context.end(subItem.task.getOutput(), subItemIndex)100                 }101                 else{102                     //先注册Task对象的完成事件103                     subItem.task.finished(function(output){104                         context.end(output, subItemIndex);105                     });106                     //再启动这个task107                     subItem.task.start(context.inputParams);108                 }109             }110         };111         this.condition = "";112         //开始执行WorkItem113         this.start = function(context){114             context.setItemsCount(_subItems.length);115             for(var i=0; i<_subItems.length; i++){116                 _startSubItem(i, context);117             }118         }119     };120 121     //异步操作上下文122     var Context = function(endCallback, inputParams){123         var _this = this;124         //Workitem里面的每一个Item会按各自的索引把返回值存到_rawOutputParams里,这样做的目的是为了方便后续操作的先决条件判断125         var _rawOutputParams = [];126         //子Item个数127         var _itemCount;128         //先决条件的判断方法集合,判断的同时也会更新outputParams129         var _condition = {130             then: function(){131                 _this.outputParams = _rawOutputParams[0].value;132                 return true;133             },134             all: function(){135                 _this.outputParams = [];136                 for(var i=0; i<_itemCount; i++){137                     if(_rawOutputParams[i]){138                         _this.outputParams[i] = _rawOutputParams[i].value;139                     }140                     else{141                         return false;142                     }143                 }144                 return true;145             },146             any: function(){147                 for(var i=0; i<_itemCount; i++){148                     if(_rawOutputParams[i]){149                         _this.outputParams = _rawOutputParams[i].value;150                         return true;151                     }152                 }153                 return false;154             }155         };156 157         //异步操作的输入操作158         this.inputParams = inputParams;159         //最终异步操作上下文结束时的输出参数(一般是一个操作的输出参数,all条件的输出参数比较特殊,是一个数组,每一个元素对应每一个子Workitem的输出参数)160         this.outputParams = null;161         this.setItemsCount = function(itemCount){162             _itemCount = itemCount;163         };164         //测试_rawOutputParams对于key条件是否通过165         this.testCondition = function(key){166             return _condition[key]();167         };168         //索引为index的子Workitem完成时会调用这个方法169         this.end = function(output, index){170             _rawOutputParams[index] = {171                 value: output172             };173             if(endCallback){174                 endCallback(output);175             }176         };177         //生成一个子Workitem执行时的上下文环境178         this.getWorkItemContext = function(index){179             //这个是子Item的上下文对象(就是this)180             return {181                 //传递给上下文的参数182                 param: _this.inputParams,183                 //调用end方法告知异步操作的完成184                 end: function(output){185                     _this.end(output, index);186                 }187             };188         };189     };190 191     //Task的状态192     var TaskStatus = {193         //未开始194         pending: 0,195         //正在进行196         doing: 1,197         //已完成198         finished: 2199     };200 201     //不定义具体形参,直接使用arguments202     window.Task = function(){203         var _status = TaskStatus.pending;204         var _wItemQueue = [], _currentItem, _currentContext;205         var _eventManager = new EventManager();206         var _output;207         //初始化一个WorkItem,并添加到执行队列中208         var _initWorkItem = function(args){209             var arrayArgs = [];210             for(var i=0; i<args.length; i++){211                 arrayArgs[i] = args[i];212             }213             var item = new WorkItem(arrayArgs);214             _wItemQueue.push(item);215             return item;216         };217         //设置item的先决条件218         var _setItemCondition = function(item, condition){219             if(condition){220                 item.condition = condition;221             }222         };223         //试着执行下一个异步操作,如果这个操作满足他的先决条件,那就执行;如果已经式最后一个异步操作了,就触发完成事件224         var _tryDoNextItem = function(output){225             var next = _getCurNextItem();226             if(next){227                 if(_currentContext.testCondition(next.condition)){228                     _currentItem = next;229                     _doCurrentItem();230                 }231             }232             else{233                 _status = TaskStatus.finished;234                 _output = output;235                 _eventManager.trigger("finish", output);236             }237         };238         //执行当前的Workitem239         var _doCurrentItem = function(contextParam){240             if(contextParam) {241                 _currentContext = new Context(_tryDoNextItem, contextParam);242             }243             else{244                 if(_currentContext){245                     _currentContext = new Context(_tryDoNextItem, _currentContext.outputParams);246                 }247                 else{248                     _currentContext = new Context(_tryDoNextItem);249                 }250             }251             _currentItem.start(_currentContext);252         };253         //获取下一个异步操作,如果已经是最后一个了返回undefined254         var _getCurNextItem = function(){255             var i=0;256             for(; i<_wItemQueue.length; i++){257                 if(_currentItem == _wItemQueue[i]){258                     break;259                 }260             }261             return _wItemQueue[i + 1];262         };263         _currentItem = _initWorkItem(arguments);264 265         //获取Task当前状态266         this.getStatus = function(){267             return _status;268         };269         //获取Task完成时的输出参数270         this.getOutput = function(){271             return _output;272         };273         //注册完成事件274         this.finished = function(callback){275             if(callback){276                 _eventManager.addHandler("finish", callback);277             }278         };279         //任务开始(把do改成start是因为在ie下有问题,ie会把do当做保留字)280         //contextParam是一个上下文参数,可以在异步方法中通过this.Param引用281         this.start = function(contextParam){282             if(_status == TaskStatus.pending){283                 _status = TaskStatus.doing;284                 _doCurrentItem(contextParam);285             }286             return this;287         };288         this.then = function(){289             var workItem = _initWorkItem(arguments);290             _setItemCondition(workItem, ‘then‘);291             return this;292         };293         this.all = function(){294             var workItem = _initWorkItem(arguments);295             _setItemCondition(workItem, ‘all‘);296             return this;297         };298         this.any = function(){299             var workItem = _initWorkItem(arguments);300             _setItemCondition(workItem, ‘any‘);301             return this;302         };303     };304 })();
View Code

除了之前的Task类之外,还添加了WorkItem类用来表示一组异步操作。代码有点多,细节请看注释。

最后给一个demo:

  1 <!DOCTYPE html>  2 <html>  3 <head>  4     <title></title>  5 </head>  6 <body>  7     <script src="http://www.mamicode.com/jquery-latest.js" type="text/javascript"></script>  8     <script type="text/javascript">  9         //promise 10         //读取文件的原始内容 11         var readFile = function(fileName){ 12             var _this = this; 13             window.setTimeout(function(){ 14                 var rawContent = "xxxxxxxx (" + fileName + ")"; 15                 console.log("read ‘" + fileName + "‘ complete. rawContent is " + rawContent); 16                 _this.end(rawContent); 17             }, 1000); 18         }; 19         //请求服务器来解析原始内容,得到真正的内容 20         var resolveFile = function(serverUrl){ 21             var _this = this; 22             var rawContent = _this.param; 23             window.setTimeout(function(){ 24                 var realContent = "Greeting (" + serverUrl + ")"; 25                 console.log("resolve file complete. realContent is " + realContent); 26                 _this.end(realContent); 27             }, 1500); 28         }; 29         //把真正的内容写入一开始的文件 30         var writeFile = function (fileName) { 31             var _this = this; 32             window.setTimeout(function(){ 33                 console.log("writeBack1 param[0] is " + _this.param[0] + " ;param[1] is " + _this.param[1]); 34                 _this.end(); 35             }, 2000); 36         }; 37         var sendMail = function(){ 38             var _this = this; 39             window.setTimeout(function(){ 40                 console.log("sendMail finished"); 41                 _this.end(); 42             }, 1000); 43         }; 44  45         //问题: 46         //1.WorkItem的参数多样化 (一些没有意义的使用形式?) 47         //2.new Task(),then,all,any方法参数可以接收Task对象 (Task完成的事件通知?) 48         //3.一组异步操作的返回值如何传递到下一组异步操作 (对于all方法如何接收前一组异步操作的所有输出参数) 49         //4.当不同状态的Task作为参数传递到另一个Task中时(未开始执行的,正在执行的,执行完成的),应该怎么处理 (Task状态管理) 50         (function(){ 51             var isFunction = function(target){ 52                 return target instanceof Function; 53             }; 54             var isArray = function(target){ 55                 return target instanceof Array; 56             }; 57  58             //自定义事件管理(代码摘抄自http://www.cnblogs.com/dolphinX/p/3254017.html) 59             var EventManager = function(){ 60                 this.handlers = {}; 61             }; 62             EventManager.prototype = { 63                 constructor: EventManager, 64                 addHandler: function(type, handler){ 65                     if(typeof this.handlers[type] == ‘undefined‘){ 66                         this.handlers[type] = new Array(); 67                     } 68                     this.handlers[type].push(handler); 69                 }, 70                 removeHandler: function(type, handler){ 71                     if(this.handlers[type] instanceof Array){ 72                         var handlers = this.handlers[type]; 73                         for(var i=0; i<handlers.length; i++){ 74                             if(handler[i] == handler){ 75                                 handlers.splice(i, 1); 76                                 break; 77                             } 78                         } 79                     } 80                 }, 81                 trigger: function(type, event){ 82                     /* 83                     if(!event.target){ 84                         event.target = this; 85                     } 86                     */ 87                     if(this.handlers[type] instanceof Array){ 88                         var handlers = this.handlers[type]; 89                         for(var i=0; i<handlers.length; i++){ 90                             handlers[i](event); 91                         } 92                     } 93                 } 94             }; 95  96             //WorkItem的参数和Task一样,有下面几种形式,不过最终存到subItems里面的元素只有两种类型.一种是方法和它的参数,一种是Task对象 97             //1.(func, [funcArg1, funcArg2]) 98             //2.(func, funcArg1, funcArg2, ....) 99             //3.(task1, task2, ....)100             //4.([func, funcArg1, funcArg2, ....]101             //   ,[func, [funcArg1, funcArg2]]102             //   ,task1103             //   , ....104             //  )105             var WorkItem = function(arrayArgs){106                 //WorkItem其实是一组异步操作的集合,_subItems就是这个集合107                 var _subItems = [];108                 var _checkFunc = function(args){109                     if(isFunction(args[0])){110                         if(args.length == 2 && isArray(args[1])){111                             _subItems.push({‘isFunc‘: true, ‘func‘: args[0], ‘args‘: args[1]});112                         }113                         else{114                             _subItems.push({‘isFunc‘: true, ‘func‘: args[0], ‘args‘: args.slice(1)});115                         }116                         return true;117                     }118                     return false;119                 };120                 var _checkTask = function(task){121                     if(task instanceof Task){122                         _subItems.push({‘isFunc‘: false, ‘task‘: task});123                     }124                 };125                 //这里是对形参的检测,看看是否满足上面的4种形式126                 if(!_checkFunc(arrayArgs)){127                     for(var i=0; i<arrayArgs.length; i++){128                         if(!_checkFunc(arrayArgs[i])){129                             _checkTask(arrayArgs[i]);130                         }131                     }132                 }133 134                 //开始执行SubItem135                 var _startSubItem = function(subItemIndex, context){136                     var subItem = _subItems[subItemIndex];137                     //如果subItem是方法和它的参数138                     if(subItem.isFunc){139                         //先获取方法的上下文环境(就是方法里面的this)140                         var workItemCxt = context.getWorkItemContext(subItemIndex);141                         //再执行方法142                         subItem.func.apply(workItemCxt, subItem.args);143                     }144                     //如果subItem是Task对象145                     else{146                         //如果task已经完成147                         if(subItem.task.getStatus() == TaskStatus.finished){148                             context.end(subItem.task.getOutput(), subItemIndex)149                         }150                         else{151                             //先注册Task对象的完成事件152                             subItem.task.finished(function(output){153                                 context.end(output, subItemIndex);154                             });155                             //再启动这个task156                             subItem.task.start(context.inputParams);157                         }158                     }159                 };160                 this.condition = "";161                 //开始执行WorkItem162                 this.start = function(context){163                     context.setItemsCount(_subItems.length);164                     for(var i=0; i<_subItems.length; i++){165                         _startSubItem(i, context);166                     }167                 }168             };169 170             //异步操作上下文171             var Context = function(endCallback, inputParams){172                 var _this = this;173                 //Workitem里面的每一个Item会按各自的索引把返回值存到_rawOutputParams里,这样做的目的是为了方便后续操作的先决条件判断174                 var _rawOutputParams = [];175                 //子Item个数176                 var _itemCount;177                 //先决条件的判断方法集合,判断的同时也会更新outputParams178                 var _condition = {179                     then: function(){180                         _this.outputParams = _rawOutputParams[0].value;181                         return true;182                     },183                     all: function(){184                         _this.outputParams = [];185                         for(var i=0; i<_itemCount; i++){186                             if(_rawOutputParams[i]){187                                 _this.outputParams[i] = _rawOutputParams[i].value;188                             }189                             else{190                                 return false;191                             }192                         }193                         return true;194                     },195                     any: function(){196                         for(var i=0; i<_itemCount; i++){197                             if(_rawOutputParams[i]){198                                 _this.outputParams = _rawOutputParams[i].value;199                                 return true;200                             }201                         }202                         return false;203                     }204                 };205 206                 //异步操作的输入操作207                 this.inputParams = inputParams;208                 //最终异步操作上下文结束时的输出参数(一般是一个操作的输出参数,all条件的输出参数比较特殊,是一个数组,每一个元素对应每一个子Workitem的输出参数)209                 this.outputParams = null;210                 this.setItemsCount = function(itemCount){211                     _itemCount = itemCount;212                 };213                 //测试_rawOutputParams对于key条件是否通过214                 this.testCondition = function(key){215                     return _condition[key]();216                 };217                 //索引为index的子Workitem完成时会调用这个方法218                 this.end = function(output, index){219                     _rawOutputParams[index] = {220                         value: output221                     };222                     if(endCallback){223                         endCallback(output);224                     }225                 };226                 //生成一个子Workitem执行时的上下文环境227                 this.getWorkItemContext = function(index){228                     //这个是子Item的上下文对象(就是this)229                     return {230                         //传递给上下文的参数231                         param: _this.inputParams,232                         //调用end方法告知异步操作的完成233                         end: function(output){234                             _this.end(output, index);235                         }236                     };237                 };238             };239 240             //Task的状态241             var TaskStatus = {242                 //未开始243                 pending: 0,244                 //正在进行245                 doing: 1,246                 //已完成247                 finished: 2248             };249 250             //不定义具体形参,直接使用arguments251             window.Task = function(){252                 var _status = TaskStatus.pending;253                 var _wItemQueue = [], _currentItem, _currentContext;254                 var _eventManager = new EventManager();255                 var _output;256                 //初始化一个WorkItem,并添加到执行队列中257                 var _initWorkItem = function(args){258                     var arrayArgs = [];259                     for(var i=0; i<args.length; i++){260                         arrayArgs[i] = args[i];261                     }262                     var item = new WorkItem(arrayArgs);263                     _wItemQueue.push(item);264                     return item;265                 };266                 //设置item的先决条件267                 var _setItemCondition = function(item, condition){268                     if(condition){269                         item.condition = condition;270                     }271                 };272                 //试着执行下一个异步操作,如果这个操作满足他的先决条件,那就执行;如果已经式最后一个异步操作了,就触发完成事件273                 var _tryDoNextItem = function(output){274                     var next = _getCurNextItem();275                     if(next){276                         if(_currentContext.testCondition(next.condition)){277                             _currentItem = next;278                             _doCurrentItem();279                         }280                     }281                     else{282                         _status = TaskStatus.finished;283                         _output = output;284                         _eventManager.trigger("finish", output);285                     }286                 };287                 //执行当前的Workitem288                 var _doCurrentItem = function(contextParam){289                     if(contextParam) {290                         _currentContext = new Context(_tryDoNextItem, contextParam);291                     }292                     else{293                         if(_currentContext){294                             _currentContext = new Context(_tryDoNextItem, _currentContext.outputParams);295                         }296                         else{297                             _currentContext = new Context(_tryDoNextItem);298                         }299                     }300                     _currentItem.start(_currentContext);301                 };302                 //获取下一个异步操作,如果已经是最后一个了返回undefined303                 var _getCurNextItem = function(){304                     var i=0;305                     for(; i<_wItemQueue.length; i++){306                         if(_currentItem == _wItemQueue[i]){307                             break;308                         }309                     }310                     return _wItemQueue[i + 1];311                 };312                 _currentItem = _initWorkItem(arguments);313 314                 //获取Task当前状态315                 this.getStatus = function(){316                     return _status;317                 };318                 //获取Task完成时的输出参数319                 this.getOutput = function(){320                     return _output;321                 };322                 //注册完成事件323                 this.finished = function(callback){324                     if(callback){325                         _eventManager.addHandler("finish", callback);326                     }327                 };328                 //任务开始(把do改成start是因为在ie下有问题,ie会把do当做保留字)329                 //contextParam是一个上下文参数,可以在异步方法中通过this.Param引用330                 this.start = function(contextParam){331                     if(_status == TaskStatus.pending){332                         _status = TaskStatus.doing;333                         _doCurrentItem(contextParam);334                     }335                     return this;336                 };337                 this.then = function(){338                     var workItem = _initWorkItem(arguments);339                     _setItemCondition(workItem, ‘then‘);340                     return this;341                 };342                 this.all = function(){343                     var workItem = _initWorkItem(arguments);344                     _setItemCondition(workItem, ‘all‘);345                     return this;346                 };347                 this.any = function(){348                     var workItem = _initWorkItem(arguments);349                     _setItemCondition(workItem, ‘any‘);350                     return this;351                 };352             };353         })();354 355 356         var taskExp_1 = new Task(readFile, ["aa.txt"]).then(resolveFile, ["/service/fileResolve.ashx?file=aa.txt"]);357         var taskExp_2 = new Task(readFile, ["bb.txt"]).then(resolveFile, ["/service/fileResolve.ashx?file=bb.txt"]);358         var taskExp_3 = new Task(taskExp_1, taskExp_2).all(writeFile, ["cc.txt"]).then(sendMail).start();359 360     </script>361 </body>362 </html>
View Code