一步一步实现基于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
posted @ 2014-07-25 22:34  lh2907883  阅读(858)  评论(4编辑  收藏  举报