一步一步实现基于Task的Promise库(五)waitFor和waitForAny的实现
在实现waitFor方法之前,我们先要搞明白下面这些问题:
1. waitFor方法的形参有限制吗?
没有!如果形参是Task类型,不应该启动Task,如果是function类型,会执行方法.所以waitFor的使用场景应该是waitFor(task1,task2),并且task1,2不知道何时启动(比如是用户点击界面按钮来启动)
2. 关于参数的传递。
1 var taskExp_0 = new Task(readFile, "123.txt"); 2 var taskExp_1 = new Task(readFile, "aa.txt").start(); 3 var taskExp_2 = new Task(readFile, "bb.txt"); 4 var taskExp_3 = new Task(taskExp_0).waitFor(taskExp_1, taskExp_2).then(writeFile).start(); 5 //taskExp_2模拟不知何时运行的Task,请在控制台运行下面代码 6 //taskExp_2.start();
上面例子中,taskExp_1,taskExp_2不接收taskExp_0的输出参数,我们希望writeFile可以通过this.Param[0],[1],[2]分别接收taskExp_0,taskExp_1,taskExp_2的输出参数。
明确了这样的设计后,下面是Task.js的实现细节和相关demo,有关waitFor和waitForAny的实现请看注释:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title></title> 5 </head> 6 <body> 7 <script type="text/javascript"> 8 //promise 9 //读取文件的原始内容 10 var uploadFile = function (fileName) { 11 var _this = this; 12 window.setTimeout(function () { 13 console.log("uploadFile '" + fileName + "' complete."); 14 _this.end(fileName); 15 }, 1800); 16 }; 17 //读取文件的原始内容 18 var readFile = function (fileName) { 19 _fileName = fileName || this.param; 20 var _this = this; 21 window.setTimeout(function () { 22 var rawContent = "xxxxxxxx (" + _fileName + ")"; 23 console.log("read '" + _fileName + "' complete. rawContent is " + rawContent); 24 _this.end(rawContent); 25 }, 1000); 26 }; 27 //请求服务器来解析原始内容,得到真正的内容 28 var resolveFile = function (serverUrl) { 29 var _this = this; 30 var rawContent = _this.param; 31 window.setTimeout(function () { 32 var realContent = "Greeting (" + serverUrl + ")"; 33 console.log("resolve file complete. realContent is " + realContent); 34 _this.end(realContent); 35 }, 1500); 36 }; 37 //把真正的内容写入文件 38 var writeFile = function (fileName) { 39 var _this = this; 40 window.setTimeout(function () { 41 console.log("writeBack1 param[0] is " + _this.param[0] + " ;param[1] is " + _this.param[1]); 42 _this.end(); 43 }, 2000); 44 }; 45 var sendMail = function () { 46 var _this = this; 47 window.setTimeout(function () { 48 console.log("sendMail finished"); 49 _this.end(); 50 }, 1000); 51 }; 52 53 (function() { 54 var isFunction = function (target) { 55 return target instanceof Function; 56 }; 57 var isArray = function (target) { 58 return target instanceof Array; 59 }; 60 61 //自定义事件管理(代码摘抄自http://www.cnblogs.com/dolphinX/p/3254017.html) 62 var EventManager = function () { 63 this.handlers = {}; 64 }; 65 EventManager.prototype = { 66 constructor: EventManager, 67 addHandler: function (type, handler) { 68 if (typeof this.handlers[type] == 'undefined') { 69 this.handlers[type] = new Array(); 70 } 71 this.handlers[type].push(handler); 72 }, 73 removeHandler: function (type, handler) { 74 if (this.handlers[type] instanceof Array) { 75 var handlers = this.handlers[type]; 76 for (var i = 0; i < handlers.length; i++) { 77 if (handler[i] == handler) { 78 handlers.splice(i, 1); 79 break; 80 } 81 } 82 } 83 }, 84 trigger: function (type, event) { 85 /* 86 if(!event.target){ 87 event.target = this; 88 } 89 */ 90 if(this.handlers[type] instanceof Array){ 91 var handlers = this.handlers[type]; 92 for(var i=0; i<handlers.length; i++){ 93 handlers[i](event); 94 } 95 } 96 } 97 }; 98 99 //所有检测条件返回{result: bool, output: obj} 100 var Condition = { 101 then: function(target){ 102 return {result: !!target[0], output: target[0].value}; 103 }, 104 all: function(target){ 105 var output = []; 106 for(var i=0; i<target.length; i++){ 107 if(target[i]){ 108 output.push(target[i].value); 109 } 110 else{ 111 return {result: false}; 112 } 113 } 114 return {result: true, output: output}; 115 }, 116 any: function(target){ 117 for(var i=0; i<target.length; i++){ 118 if(target[i]){ 119 return {result: true, output: target[i].value}; 120 } 121 } 122 return {result: false}; 123 } 124 }; 125 126 //option:{ 127 // autoStart: bool, //自动启动 128 // keepInputParams: bool //是否把上一步传递过来的input传递给它的下一步 129 //} 130 //finishedCallback 表示WorkItem满足条件并结束的回调 131 var WorkItem = function(arrayArgs, finishedCallback, option){ 132 var _subItems = []; 133 var _rawOutputParams = []; 134 //完成WorkItem的检测条件 135 var _condition; 136 var _input; 137 option = option || {}; 138 //增加一个bool类型的属性autoStart,默认值为true,表示当调用_startSubItem时是否自动执行subItem(当subItem是task时根据autoStart参数来执行task,如果subItem是方法时,不管autoStart是什么都会执行) 139 option.autoStart = option.autoStart !== false; 140 //是否把上一步传递过来的input传递给它的下一步,默认false 141 option.keepInput = option.keepInput === true; 142 143 var _checkFunc = function(args){ 144 if(isFunction(args[0])){ 145 if(args.length == 2 && isArray(args[1])){ 146 _subItems.push({'isFunc': true, 'func': args[0], 'args': args[1]}); 147 } 148 else{ 149 _subItems.push({'isFunc': true, 'func': args[0], 'args': args.slice(1)}); 150 } 151 return true; 152 } 153 return false; 154 }; 155 var _checkTask = function(task){ 156 if(task instanceof Task){ 157 _subItems.push({'isFunc': false, 'task': task}); 158 } 159 }; 160 if(!_checkFunc(arrayArgs)){ 161 for(var i=0; i<arrayArgs.length; i++){ 162 if(!_checkFunc(arrayArgs[i])){ 163 _checkTask(arrayArgs[i]); 164 } 165 } 166 } 167 _rawOutputParams.length = _subItems.length; 168 169 var _startSubItem = function(subItemIndex){ 170 var subItem = _subItems[subItemIndex]; 171 if(subItem.isFunc){ 172 var workItemCxt = _getSubItemContext(subItemIndex); 173 subItem.func.apply(workItemCxt, subItem.args); 174 } 175 else{ 176 if(subItem.task.getStatus() == TaskStatus.finished){ 177 _endSubItem(subItem.task.getOutput(), subItemIndex) 178 } 179 else{ 180 subItem.task.finished(function(output){ 181 _endSubItem(output, subItemIndex); 182 }); 183 if(option.autoStart){ 184 subItem.task.start(_input); 185 } 186 } 187 } 188 }; 189 var _endSubItem = function(output, index){ 190 _rawOutputParams[index] = { 191 'value': output 192 }; 193 var testResult = Condition[_condition](_rawOutputParams); 194 if(testResult.result){ 195 _onFinishedCallback(testResult.output); 196 } 197 }; 198 var _merge = function(target, data){ 199 if(data){ 200 if(isArray(data)){ 201 for(var i=0; i<data.length; i++){ 202 target.push(data[i]); 203 } 204 } 205 else{ 206 target.push(data); 207 } 208 } 209 }; 210 var _onFinishedCallback = function(output){ 211 //如果需要保留输入参数,那么需要对输入和输出参数来一个合并 212 if(option.keepInput){ 213 var result = []; 214 _merge(result, _input); 215 _merge(result, output); 216 if(result.length == 0){ 217 output = undefined; 218 } 219 else{ 220 output = result; 221 } 222 } 223 finishedCallback(output) 224 }; 225 var _getSubItemContext = function(index){ 226 return { 227 param: _input, 228 end: function(output){ 229 _endSubItem(output, index); 230 } 231 }; 232 }; 233 234 this.setCondition = function(condition){ 235 _condition = condition; 236 }; 237 this.start = function(input){ 238 _input = input; 239 for(var i=0; i<_subItems.length; i++){ 240 _startSubItem(i); 241 } 242 }; 243 }; 244 var ConditionWorkItem = function(finishedCallback){ 245 this.start = function(input){ 246 finishedCallback(input); 247 }; 248 }; 249 250 var TaskStatus = { 251 //未开始 252 pending: 0, 253 //正在进行 254 doing: 1, 255 //已完成 256 finished: 2 257 }; 258 259 window.Task = function(){ 260 var _status = TaskStatus.pending; 261 var _wItemQueue = [], _currentItem; 262 var _eventManager = new EventManager(); 263 var _output; 264 //设置_wItemQueue队列的最后一个WorkItem的完成条件 265 var _setLastItemCondition = function(condition){ 266 if(condition != null){ 267 var last = _wItemQueue[_wItemQueue.length - 1]; 268 //因为ConditionWorkItem是没有setCondition方法的(它也不需要判断条件),所以有这个if 269 if(last.setCondition){ 270 last.setCondition(condition); 271 } 272 } 273 }; 274 var _initWorkItem = function(condition, args, option){ 275 _setLastItemCondition(condition); 276 var item; 277 if(args.length == 0){ 278 item = new ConditionWorkItem(_finishCallback); 279 } 280 else{ 281 var arrayArgs = []; 282 for(var i=0; i<args.length; i++){ 283 arrayArgs[i] = args[i]; 284 } 285 item = new WorkItem(arrayArgs, _finishCallback, option); 286 } 287 _wItemQueue.push(item); 288 return item; 289 }; 290 //WorkItem完成的回调 291 var _finishCallback = function(output){ 292 var next = _getCurNextItem(); 293 if(next){ 294 //如果有下一个WorkItem,就start它 295 _currentItem = next; 296 _currentItem.start(output); 297 } 298 else{ 299 //如果没有就通知Task结束 300 _status = TaskStatus.finished; 301 _output = output; 302 _eventManager.trigger("finish", output); 303 } 304 }; 305 var _getCurNextItem = function(){ 306 var i=0; 307 for(; i<_wItemQueue.length; i++){ 308 if(_currentItem == _wItemQueue[i]){ 309 break; 310 } 311 } 312 return _wItemQueue[i + 1]; 313 }; 314 _currentItem = _initWorkItem(null, arguments); 315 316 this.getStatus = function(){ 317 return _status; 318 }; 319 this.getOutput = function(){ 320 return _output; 321 }; 322 this.finished = function(callback){ 323 if(callback){ 324 _eventManager.addHandler("finish", callback); 325 } 326 }; 327 this.start = function(input){ 328 if(_status == TaskStatus.pending){ 329 _status = TaskStatus.doing; 330 //start的时候给最后一个WorkItem设置then条件 331 _setLastItemCondition("then"); 332 _currentItem.start(input); 333 } 334 return this; 335 }; 336 this.waitFor = function(){ 337 //先初始化一个不会自启动的WorkItem,并且这个WorkItem把上一步传递过来的input传递给它的下一步 338 //进入这个WorkItem的条件是then,如果你想要进入条件是all,可以这样 xxxxx.all().waitFor(task1).xxxxx 339 _initWorkItem("then", arguments, {autoStart: false, keepInput: true}); 340 //最后调用all()表示这个WorkItem里面的所有子Item必须都完成才能继续下一步 341 return this.all(); 342 }; 343 this.waitForAny = function(){ 344 _initWorkItem("then", arguments, {autoStart: false, keepInput: true}); 345 //前面和waitFor的逻辑一样,最后调用any()表示这个WorkItem里面的所有子Item完成其中一个就可以进入下一步 346 return this.any(); 347 }; 348 this.then = function(){ 349 _initWorkItem('then', arguments); 350 return this; 351 }; 352 this.all = function(){ 353 _initWorkItem('all', arguments); 354 return this; 355 }; 356 this.any = function(){ 357 _initWorkItem('any', arguments); 358 return this; 359 }; 360 }; 361 })(); 362 363 var taskExp_1 = new Task(readFile, "aa.txt").then(resolveFile, "/service/fileResolve.ashx?file=aa.txt"); 364 var taskExp_2 = new Task(uploadFile, "bb.txt").then(readFile, "bb.txt").then(resolveFile, "/service/fileResolve.ashx?file=bb.txt"); 365 var taskExp_3 = new Task(taskExp_1).waitFor(taskExp_2).then(writeFile, ["cc.txt"]).then(sendMail).start(); 366 //taskExp_2模拟不知何时运行的Task,请在控制台运行下面代码 367 //taskExp_2.start(); 368 369 </script> 370 </body> 371 </html>