在上一篇中我们已经初步完成了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方法之前,先看一看我们将要面临哪些问题:
- 现在new Task(),then(),all(),any()方法的形参可以是多个异步操作了,并且每个异步操作可以是一个方法和它的参数,也可以是一个Task。
- 既然这些方法的形参可以是Task,那么我们理所当然要支持Task的完成通知。
- 一组异步操作的返回值如何传递到下一组异步操作 ,对于all方法如何接收前一组异步操作的所有输出参数呢?在上一章里我们通过this.param接收参数,这里对于then,any方法同样如此,因为最终它们只会有一个异步操作的输出参数传递到下一个异步操作,all方法比较特殊,我斟酌好久决定使用this.param[0],this.param[1]... 来接收同组的不同异步操作的输出参数
- 当不同状态的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 //再启动这个task 107 subItem.task.start(context.inputParams); 108 } 109 } 110 }; 111 this.condition = ""; 112 //开始执行WorkItem 113 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 //先决条件的判断方法集合,判断的同时也会更新outputParams 129 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: output 172 }; 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: 2 199 }; 200 201 //不定义具体形参,直接使用arguments 202 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 //执行当前的Workitem 239 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 //获取下一个异步操作,如果已经是最后一个了返回undefined 254 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 })();