开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 1319|回复: 1
收起左侧

[JS例程分享] 异步javascript的原理和实现

[复制链接]

结帖率:61% (35/57)
发表于 2013-1-8 21:08:47 | 显示全部楼层 |阅读模式   海南省海口市
本帖最后由 小松鼠 于 2013-1-8 21:24 编辑

首 先回顾我的数据批量提交的需求:我有一批用户数据要插入到系统中,但是因为系统库表结构不是行列式的,所以无法转化为sql语句插入。要插入的数据有接近 200条,就是傻呵呵地手工录入到系统,估计也要1天的时间。作为程序员,当然不会干这么傻的事情,我一定要用程序来解决。这个编程的过程耗费了我1天的 时间。相比手工录入,我额外收入是这篇博文,绝对的合算!
编程平台选择没花费时间,直接选定基于Greasemonkey写自己的脚本,浏览器当然是firefox了。脚本的工作过程:
   在脚本中预先存放要插入的数据
   模拟鼠标点击,打开页面中的输入窗口
   将数据录入到输入窗口,并模拟点击“提交”按钮,将数据提交到系统中。
   依次循环,直到所有数据都处理完毕。
这里的技术难点在于:
   打开输入窗口,需要等待不定期的时间,视网络情况而定。
   提交数据到后台,需要等待处理完毕之后才可以循环下一个数据。
如果我是菜鸟的话,我当然直接写一个类似这样的应用逻辑:
  1:  for(var i = 0; i < dataArray.length; ++i)
  2:  {
  3:      clickButtonForInputWindow();
  4:      waitInputWindow();
  5:      enterInputData(dataArray
);
  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的异步函数库。
说异步之前,我们一起回忆一下同步操作的几种结构类型:
   顺序:就是语句的先后顺序执行
   判断:就是判断语句
   循环:严格来说应该是跳转(goto),但大多数现代语言都取消了goto。循环其实应该是复合结构,是if和goto的组合体。
异步操作的难点在两个地方:
   异步的判断:异步情况下的判断基本都是检测条件十分满足,然后执行某些动作。
   异步的顺序:顺序中的每一步操作之后都要交回控制权,等待在下一个时间片中继续执行下一步。难点是如何保持顺序性。尤其在两个顺序动作中间夹杂一个异步的循环的时候。
   异步的循环:每次循环之后都交回控制权到浏览器,如此循环,直到运行结束。
最简单的实现当然就是异步循环了,我的实现代码如下:
  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: ret
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
, 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(aSyncSe


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




点评

没人和你强   浙江省丽水市  发表于 2013-1-8 21:13

结帖率:75% (3/4)
发表于 2013-1-8 21:22:17 | 显示全部楼层   黑龙江省齐齐哈尔市
这个 字体吧,啧啧,  有爱。 哈哈 没人和你抢。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 诚聘英才| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报电话0663-3422125,QQ: 800073686,邮箱:800073686@b.qq.com
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表