首页 > 代码库 > 一步一步实现基于Task的Promise库(一)Promise的基本实现

一步一步实现基于Task的Promise库(一)Promise的基本实现

如果我们现在有一个需求,大概是先读取一个文件的内容,再把得到的内容传给后台去解析,最后把解析后的结果再保存到那个文件,按照最原始的做法代码就是下面这个样子的:

 1 //读取文件的原始内容 2 var readFile = function(fileName, callback){ 3     window.setTimeout(function(){ 4         console.log("read ‘" + fileName + "‘ complete."); 5         var rawContent = "..... content ......"; 6         if(callback){ 7             callback(rawContent); 8         } 9     }, 2000);10 };11 //请求服务器来解析原始内容,得到真正的内容12 var resolveFile = function(serverUrl, rawContent, callback){13     window.setTimeout(function(){14         console.log("resolve complete.");15         var realContent = "..... 内容 .....";16         if(callback){17             callback(realContent);18         }19     }, 1000);20 };21 //把真正的内容写入一开始的文件22 var writeBack = function(fileName, realContent, callback){23     window.setTimeout(function(){24         console.log("writeBack complete.");25         if(callback){26             callback();27         }28     }, 2000);29 };30 readFile("aa.txt", function(rawContent){31     resolveFile("/service/fileResolve.ashx", rawContent, function(realContent){32         writeBack("aa.txt", realContent, function(){33             //给个完工的通知34             alert("everything is ok.");35         });36     });37 });
View Code

这里我全部采用window.setTimeout来模拟一个异步操作,然而这种嵌套回调方法的做法看起来非常丑陋,如果能改掉嵌套的形式,采用链式调用会美观很多。因此我们期望的调用形式是下面这个样子:

 1 //期望的调用形式(一) Promise的基本实现 2  var taskExp1_1 = new Task(readFile, ["aa.txt"]) 3          .then(resolveFile, ["/service/fileResolve.ashx"]) 4          .then(writeBack, ["aa.txt"]) 5          .then(function(){ 6              alert("everything is ok."); 7              this.end(); 8          }) 9          //do方法才是正真的执行这一组异步调用,不调用do方法相当于只是配置一组异步调用10          .do();

这种异步方法的链式调用实际上就是一个Promise的实现,只不过这里是通过一个Task类去完成的,我们的目标就是实现这个Task类,它包含了一组有先后逻辑依赖的异步操作,then方法里面传递的function并不会因为then的执行而执行,实际上then方法可以看做是对一组有先后逻辑依赖的异步操作的一个配置,真正导致执行的是do方法,调用do方法会从这个队列的头部开始调用,而标致一个异步操作的结束是在异步操作方法里面,看下面的代码:

 1 //读取文件的原始内容 2 var readFile = function(fileName){ 3     var _this = this; 4     window.setTimeout(function(){ 5         var rawContent = "xxxxxxxx (" + fileName + ")"; 6         console.log("read ‘" + fileName + "‘ complete. rawContent is " + rawContent); 7         //告知异步调用已经完成 8         _this.end(rawContent); 9     }, 2000);10 };

我们稍微对readFile方法做了修改,当文件读取完成的时候调用this.end方法通知异步操作的完成,这样Task就知道该进行下一个异步操作了,就会执行resolveFile方法,那么这里有一个问题就是readFile方法需要传递一个参数rawContent给resolveFile方法,可以看到this.end(rawContent);这句代码已经有传递,resolveFile方法如何接收呢?

 1 //请求服务器来解析原始内容,得到真正的内容 2 var resolveFile = function(serverUrl){ 3     var _this = this; 4     //可以从params属性中获取上一个异步调用传递过来的参数 5     var rawContent = _this.params; 6     window.setTimeout(function(){ 7         var realContent = "Greeting (" + serverUrl + ")"; 8         console.log("resolve file complete. realContent is " + realContent); 9         _this.end(realContent);10     }, 1000);11 };

 可以看到resolveFile方法通过this.params接收readFile的输出参数。

到目前为止,我们看到的都是如何使用Task类,那么我们最希望有一个什么样的库来完成这种逻辑配置关系呢? 除了上面说的传参问题,还有一个就是我希望每一个异步操作都可以接收一些形参,这样我们使用Task类的时候就不用自己拐弯抹角的塞参数了,否则我们可能要这样写:

 1 var taskExp1_1 = new Task(function (){ 2         readFile.call(this, "aa.txt"); 3     }).then(function (){ 4         resolveFile.call(this, "/service/fileResolve.ashx"); 5     }).then(function (){ 6         writeBack.call(this, "aa.txt"); 7     }).then(function () { 8         alert("everything is ok."); 9         this.end();10     })11     .do();

下面是整个demo和Task类的实现细节:

  1 <script type="text/javascript">  2     //读取文件的原始内容  3     var readFile = function(fileName){  4         var _this = this;  5         window.setTimeout(function(){  6             var rawContent = "xxxxxxxx (" + fileName + ")";  7             console.log("read ‘" + fileName + "‘ complete. rawContent is " + rawContent);  8             _this.end(rawContent);  9         }, 2000); 10     }; 11     //请求服务器来解析原始内容,得到真正的内容 12     var resolveFile = function(serverUrl){ 13         var _this = this; 14         var rawContent = _this.params; 15         window.setTimeout(function(){ 16             var realContent = "Greeting (" + serverUrl + ")"; 17             console.log("resolve file complete. realContent is " + realContent); 18             _this.end(realContent); 19         }, 1000); 20     }; 21     //把真正的内容写入一开始的文件 22     var writeBack = function(fileName){ 23         var _this = this; 24         var realContent = _this.params; 25         window.setTimeout(function(){ 26             console.log("writeBack complete."); 27             _this.end(); 28         }, 2000); 29     }; 30     var WorkItem = function(func, args){ 31         return { 32             //表示执行此异步操作的先决条件 33             condition: "", 34             //表示当前异步操作是否执行完了 35             isDone: false, 36             //正真的执行 37             ‘do‘: function(context){ 38                 func.call(context, args); 39             } 40         }; 41     }; 42     var Task = function(func, args){ 43         //Task内部会维护一个异步方法的队列,此队列严格按照先后顺序执行 44         var wItemQueue = []; 45         //当前异步方法 46         var currentItem; 47         //执行异步方法前要判断的先决条件集合(目前只有then) 48         var condition = { 49             //直接执行 50             then: function(workItem){ 51                 return true; 52             } 53         }; 54         //初始化一个异步操作,这个方法主要处理接受参数的多样性 55         var _initWorkItem = function(func, args, condition){ 56             if(func instanceof Task){ 57                 return null; 58             } 59             else{ 60                 return _enqueueItem(new WorkItem(func, args), condition); 61             } 62         }; 63         //记录异步操作的先决条件,并添加到队列中去 64         var _enqueueItem = function(item, condition){ 65             if(condition){ 66                 item.condition = condition; 67             } 68             wItemQueue.push(item); 69             return item; 70         }; 71         //试着执行下一个异步操作,如果这个操作满足他的先决条件,那就执行 72         var _tryDoNextItem = function(context){ 73             var next = _getCurNextItem(); 74             if(next){ 75                 if(condition[next.condition](next)){ 76                     currentItem = next; 77                     currentItem.do(context); 78                 } 79             } 80         }; 81         //获取下一个异步操作,如果已经是最后一个了返回undefined 82         var _getCurNextItem = function(){ 83             var i=0; 84             for(; i<wItemQueue.length; i++){ 85                 if(currentItem == wItemQueue[i]){ 86                     break; 87                 } 88             } 89             return wItemQueue[i + 1]; 90         }; 91         //定义异步操作的上下文环境 92         var Context = function(){}; 93         Context.prototype = { 94             //上一个异步调用传递过来的参数 95             ‘params‘: null, 96             //执行此方法就表示当前异步操作已经完成,那么会尝试执行下一个异步操作 97             end: function(output){ 98                 currentItem.isDone = true; 99                 this.params = output;100                 _tryDoNextItem(this);101                 return this;102             }103         };104         currentItem = _initWorkItem(func, args);105 106         //Task的公共方法,这些方法都应该支持链式调用(都返回this)107         return {108             //开始执行109             ‘do‘: function(){110                 if(currentItem && currentItem.condition == ""){111                     currentItem.do(new Context());112                 }113                 return this;114             },115             //配置下一个异步操作116             then: function(func, args){117                 _initWorkItem(func, args, ‘then‘);118                 return this;119             }120         };121     };122 123     var taskExp_1 = new Task(readFile, ["aa.txt"])124             .then(resolveFile, ["/service/fileResolve.ashx"])125             .then(writeBack, ["aa.txt"])126             .then(function(){127                 alert("everything is ok.");128                 this.end();129             })130             .do();131 </script>
View Code

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