zoukankan      html  css  js  c++  java
  • 异步javascript的原理和实现

    因为工作的需要,我要在网页端编写一段脚本,把数据通过网页批量提交到系统中去。所以我就想到了Greasemonkey插件,于是就开始动手写,发现问题解决得很顺利。但是在对脚本进行总结和整理的时候,我习惯性地问了自己一个问题:能不能再简单点?

    我的答案当然是“能”。

    首先回顾我的数据批量提交的需求:我有一批用户数据要插入到系统中,但是因为系统库表结构不是行列式的,所以无法转化为sql语句插入。要插入的数据有接近200条,就是傻呵呵地手工录入到系统,估计也要1天的时间。作为程序员,当然不会干这么傻的事情,我一定要用程序来解决。这个编程的过程耗费了我1天的时间。相比手工录入,我额外收入是这篇博文,绝对的合算!

    编程平台选择没花费时间,直接选定基于Greasemonkey写自己的脚本,浏览器当然是firefox了。脚本的工作过程:

    1. 在脚本中预先存放要插入的数据
    2. 模拟鼠标点击,打开页面中的输入窗口
    3. 将数据录入到输入窗口,并模拟点击“提交”按钮,将数据提交到系统中。
    4. 依次循环,直到所有数据都处理完毕。

    这里的技术难点在于:

    1. 打开输入窗口,需要等待不定期的时间,视网络情况而定。
    2. 提交数据到后台,需要等待处理完毕之后才可以循环下一个数据。

    如果我是菜鸟的话,我当然直接写一个类似这样的应用逻辑:

       1:  for(var i = 0; i < dataArray.length; ++i)
       2:  {
       3:      clickButtonForInputWindow();
       4:      waitInputWindow();
       5:      enterInputData(dataArray[i]);
       6:      clickSubmitButton();
       7:      waitInputWindowClose();
       8:  }

    实际上这样写所有浏览器都会陷入一片白屏,并在若干分钟之后提示“没有响应”而被强行终止掉。原因就是浏览器在调用javascript的时候,主界面是停止响应的,因为cpu交给js执行了,没有时间去处理界面消息。

    为了满足“不锁死”的要求,我们可以把脚本修改成这样:

       1:  for(var i = 0; i < dataArray.length; ++i)
       2:  {
       3:      setTimeout(clickButtonForInputWindow);
       4:  
       5:      setTimeout(waitInputWindowClose);
       6:  }

    实际上setTimeout和setInterval是浏览器唯一可以支持异步的操作。如何更优雅地使用这两个函数来实现异步操作呢?目前简单的答案是老赵Wind.js。虽然我没有用过这个函数库,但是光是$await调用,就是符合我一贯对简洁的要求的。但是对于我这样的单个文件的脚本来说,去网上下载一个外部js库,明显不如有一段支持异步操作的代码拷贝过来的快和爽。

    所以我决定另辟蹊径,做一个不要编译而且易用性还可以更能够Copy&Paste的异步函数库。

    说异步之前,我们一起回忆一下同步操作的几种结构类型:

    1. 顺序:就是语句的先后顺序执行
    2. 判断:就是判断语句
    3. 循环:严格来说应该是跳转(goto),但大多数现代语言都取消了goto。循环其实应该是复合结构,是if和goto的组合体。

    异步操作的难点在两个地方:

    1. 异步的判断:异步情况下的判断基本都是检测条件十分满足,然后执行某些动作。
    2. 异步的顺序:顺序中的每一步操作之后都要交回控制权,等待在下一个时间片中继续执行下一步。难点是如何保持顺序性。尤其在两个顺序动作中间夹杂一个异步的循环的时候。
    3. 异步的循环:每次循环之后都交回控制权到浏览器,如此循环,直到运行结束。

    最简单的实现当然就是异步循环了,我的实现代码如下:

       1:  function asyncWhile(fn, interval) 
       2:  { 
       3:      if( fn == null || (typeof(fn) != "string" && typeof(fn) != "function") ) 
       4:          return; 
       5:      var wrapper = function() 
       6:      { 
       7:          if( (typeof(fn) == "function" ? fn() : eval(fn) ) !== false ) 
       8:              setTimeout(wrapper, interval == null? 1: interval); 
       9:      } 
      10:      wrapper(); 
      11:  }
    核心内容就是:如果fn函数返回值不是false,就继续下一个setTimeout的登记调用。

    实际上,“等待并执行”逻辑,根本上就是一个异步循环问题。这种情况的实现方法示例如下:

       1:  asyncWhile(function(){
       2:      if( xxxCondition == false )
       3:          return true; // 表示继续循环
       4:      else
       5:          doSomeThing();
       6:      return false; // 表示不需要继续循环了
       7:  });

    对于非等待并执行的逻辑,简单一个 setTimeout 就可以了。

    异步容易,实现异步中的顺序才叫难度呢。最早的起因是我要实现3步,但是第二部是一个异步的100多次的循环。也就是说,我要实现的3步操作,其实是103次的顺序异步操作。为了一个如何在浏览器中实现可响应的等待,找破了脑袋,只找到一个firefox中的实现,还要申请特权调用。

    最后想出了一个简单的方法,就是引入了“执行链(Execution Chain)”的概念,同一个执行链的所有登记函数是顺序的,不同执行链之间没有任何关系。另外,不提供互斥(mutex)等概念,如果要同步,自行在代码中检查。

    在同一个执行链中,保存一个执行令牌,只有令牌和函数序号匹配,才允许执行,这样就保证了异步执行的顺序性。

       1:      function asyncSeq(funcArray, chainName, abortWhenError)
       2:      {
       3:          if( typeof(funcArray) == "function" )
       4:              return asyncSeq([funcArray], chainName, abortWhenError);
       5:              
       6:          if( funcArray == null || funcArray.length == 0 ) 
       7:              return;
       8:              
       9:          if( chainName == null ) chainName = "__default_seq_chain__";
      10:          var tInfos = asyncSeq.chainInfos = asyncSeq.chainInfos || {};
      11:          var tInfo = tInfos[chainName] = tInfos[chainName] || {count : 0, currentIndex : -1, abort : false};
      12:          
      13:          for(var i = 0; i < funcArray.length; ++i)
      14:          {
      15:              asyncWhile(function(item, tIndex){
      16:                  return function(){
      17:                      if( tInfo.abort )
      18:                          return false;
      19:                      if( tInfo.currentIndex < tIndex )
      20:                          return true;
      21:                      else if( tInfo.currentIndex == tIndex )
      22:                      {
      23:                          try{
      24:                              item();
      25:                          }
      26:                          catch(e){
      27:                              if( abortWhenError ) tInfo.abort = true;
      28:                          }
      29:                          finally{
      30:                              tInfo.currentIndex ++;
      31:                          }
      32:                      }
      33:                      else
      34:                      {
      35:                          if( abortWhenError ) tInfo.abort = true;
      36:                      }
      37:                      return false;
      38:                  };
      39:              }(funcArray[i], tInfo.count ++));
      40:          }
      41:          
      42:          setTimeout(function(){
      43:        if( tInfo.count > 0 && tInfo.currentIndex == -1 )
      44:                  tInfo.currentIndex = 0;
      45:          },20); // 为了调试的原因,加了延迟启动
      46:      }

    由此,一个支持Copy&Paste的异步js函数库就完成了。具体的使用例子如下:

       1:      function testAsync()
       2:      {        
       3:          asyncSeq([function(){println("aSyncSeq -0 ");}
       4:              , function(){println("aSyncSeq -1 ");}
       5:              , function(){println("aSyncSeq -2 ");}
       6:              , function(){println("aSyncSeq -3 ");}
       7:              , function(){println("aSyncSeq -4 ");}
       8:              , function(){println("aSyncSeq -5 ");}
       9:              , function(){println("aSyncSeq -6 ");}
      10:              , function(){println("aSyncSeq -7 ");}
      11:              , function(){println("aSyncSeq -8 ");}
      12:              , function(){println("aSyncSeq -9 ");}
      13:              , function(){println("aSyncSeq -10 ");}
      14:              , function(){println("aSyncSeq -11 ");}
      15:              , function(){println("aSyncSeq -12 ");}
      16:              , function(){println("aSyncSeq -13 ");}
      17:              , function(){println("aSyncSeq -14 ");}
      18:              , function(){println("aSyncSeq -15 ");}
      19:              , function(){println("aSyncSeq -16 ");}
      20:              , function(){println("aSyncSeq -17 ");}
      21:              , function(){println("aSyncSeq -18 ");}
      22:              , function(){println("aSyncSeq -19 ");}
      23:              , function(){println("aSyncSeq -20 ");}
      24:              , function(){println("aSyncSeq -21 ");}
      25:              , function(){println("aSyncSeq -22 ");}
      26:              , function(){println("aSyncSeq -23 ");}
      27:              , function(){println("aSyncSeq -24 ");}
      28:              , function(){println("aSyncSeq -25 ");}
      29:              , function(){println("aSyncSeq -26 ");}
      30:              , function(){println("aSyncSeq -27 ");}
      31:              , function(){println("aSyncSeq -28 ");}
      32:              , function(){println("aSyncSeq -29 ");}
      33:          ]);
      34:   
      35:          asyncSeq([function(){println("aSyncSeq test-chain -a0 ");}
      36:              , function(){println("aSyncSeq test-chain -a1 ");}
      37:              , function(){println("aSyncSeq test-chain -a2 ");}
      38:              , function(){println("aSyncSeq test-chain -a3 ");}
      39:              , function(){println("aSyncSeq test-chain -a4 ");}
      40:              , function(){println("aSyncSeq test-chain -a5 ");}
      41:              , function(){println("aSyncSeq test-chain -a6 ");}
      42:              , function(){println("aSyncSeq test-chain -a7 ");}
      43:              , function(){println("aSyncSeq test-chain -a8 ");}
      44:          ], "test-chain");
      45:   
      46:          asyncSeq([function(){println("aSyncSeq -a0 ");}
      47:              , function(){println("aSyncSeq -a1 ");}
      48:              , function(){println("aSyncSeq -a2 ");}
      49:              , function(){println("aSyncSeq -a3 ");}
      50:              , function(){println("aSyncSeq -a4 ");}
      51:              , function(){println("aSyncSeq -a5 ");}
      52:              , function(){println("aSyncSeq -a6 ");}
      53:              , function(){println("aSyncSeq -a7 ");}
      54:              , function(){println("aSyncSeq -a8 ");}
      55:          ]);
      56:      }
      57:   
      58:      var textArea = null;
      59:      
      60:      function println(text)
      61:      {
      62:          if( textArea == null )
      63:          {
      64:              textArea = document.getElementById("text");
      65:              textArea.value = "";
      66:          }
      67:          
      68:          textArea.value = textArea.value + text + "\r\n";
      69:      }

    最后,要向大家说一声抱歉,很多只想拿代码的朋友恐怕要失望了,如果你真的不知道怎么处理这些多余的行号,你可以学习一下正则表达式的替换,推荐用UltraEdit。


    公众号:老翅寒暑
  • 相关阅读:
    Rabbitmq
    eclipse查看源码
    winSCP:无权访问
    jdk1.8.0环境变量设置
    【Linux】开放指定端口设置
    Redis (windows)安装
    [转]Windows8下设置VS默认启动方式为管理员启动
    [转]varchar(n),nvarchar(n) 长度、性能、及所占空间分析
    [转]Linq中GroupBy方法的使用总结
    [转]人人网首页拖拽上传详解(HTML5 Drag&Drop、FileReader API、formdata)
  • 原文地址:https://www.cnblogs.com/BigTall/p/2759768.html
Copyright © 2011-2022 走看看