• 一步一步实现基于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
  • 相关阅读:
    刷新aspx页面的六种方法
    关于 Visual Studio 2012 新建的网站项目在 Visual Studio 2010 中的兼容性问题
    Bing Maps 辅助工具 —— MapCruncher 简单使用学习
    学习使用Bing Maps Silverlight Control(七):自定义导航工具栏
    学习使用Bing Maps Silverlight Control(五):离线使用和自定义地图模式
    使用参数初始化Silverlight程序
    WPF中资源文件的使用
    关于特殊符号在XML中的问题
    学习使用Bing Maps Silverlight Control(六):自定义“鹰眼”地图
    关于Sliverlight的一些问题
  • 原文地址:https://www.cnblogs.com/lihao123/p/3869105.html
Copyright © 2020-2023  润新知