一步一步实现基于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方法之前,先看一看我们将要面临哪些问题:
- 现在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 })();
除了之前的Task类之外,还添加了WorkItem类用来表示一组异步操作。代码有点多,细节请看注释。
最后给一个demo:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title></title> 5 </head> 6 <body> 7 <script src="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 // ,task1 103 // , .... 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 //开始执行SubItem 135 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 //再启动这个task 156 subItem.task.start(context.inputParams); 157 } 158 } 159 }; 160 this.condition = ""; 161 //开始执行WorkItem 162 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 //先决条件的判断方法集合,判断的同时也会更新outputParams 178 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: output 221 }; 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: 2 248 }; 249 250 //不定义具体形参,直接使用arguments 251 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 //执行当前的Workitem 288 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 //获取下一个异步操作,如果已经是最后一个了返回undefined 303 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>