异步javascript的原理和实现技巧介绍


Posted in Javascript onNovember 08, 2012

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

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

编程平台选择没花费时间,直接选定基于Greasemonkey写自己的脚本,浏览器当然是firefox了。脚本的工作过程:
在脚本中预先存放要插入的数据
模拟鼠标点击,打开页面中的输入窗口
将数据录入到输入窗口,并模拟点击“提交”按钮,将数据提交到系统中。
依次循环,直到所有数据都处理完毕。

这里的技术难点在于:
打开输入窗口,需要等待不定期的时间,视网络情况而定。
提交数据到后台,需要等待处理完毕之后才可以循环下一个数据。
如果我是菜鸟的话,我当然直接写一个类似这样的应用逻辑:

for(var i = 0; i < dataArray.length; ++i) 
{ 3: clickButtonForInputWindow(); 
waitInputWindow(); 
enterInputData(dataArray[i]); 
clickSubmitButton(); 
waitInputWindowClose(); 
}

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

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

for(var i = 0; i < dataArray.length; ++i) 
{ 
setTimeout(clickButtonForInputWindow); 
… 
setTimeout(waitInputWindowClose); 
}

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

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

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

顺序:就是语句的先后顺序执行
判断:就是判断语句
循环:严格来说应该是跳转(goto),但大多数现代语言都取消了goto。循环其实应该是复合结构,是if和goto的组合体。
异步操作的难点在两个地方:

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

function asyncWhile(fn, interval) 
{ 
if( fn == null || (typeof(fn) != "string" && typeof(fn) != "function") ) 
return; 
var wrapper = function() 
{ 
if( (typeof(fn) == "function" ? fn() : eval(fn) ) !== false ) 
setTimeout(wrapper, interval == null? 1: interval); 
} 
wrapper(); 
}

核心内容就是:如果fn函数返回值不是false,就继续下一个setTimeout的登记调用。

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

asyncWhile(function(){ 
if( xxxCondition == false ) 
return true; // 表示继续循环 
else 
doSomeThing(); 
return false; // 表示不需要继续循环了 
});

对于非等待并执行的逻辑,简单一个 setTimeout 就可以了。
异步容易,实现异步中的顺序才叫难度呢。最早的起因是我要实现3步,但是第二部是一个异步的100多次的循环。也就是说,我要实现的3步操作,其实是103次的顺序异步操作。为了一个如何在浏览器中实现可响应的等待,找破了脑袋,只找到一个firefox中的实现,还要申请特权调用。
最后想出了一个简单的方法,就是引入了“执行链(Execution Chain)”的概念,同一个执行链的所有登记函数是顺序的,不同执行链之间没有任何关系。另外,不提供互斥(mutex)等概念,如果要同步,自行在代码中检查。
在同一个执行链中,保存一个执行令牌,只有令牌和函数序号匹配,才允许执行,这样就保证了异步执行的顺序性。
function asyncSeq(funcArray, chainName, abortWhenError) 
{ 
if( typeof(funcArray) == "function" ) 
return asyncSeq([funcArray], chainName, abortWhenError); if( funcArray == null || funcArray.length == 0 ) 
return; 
if( chainName == null ) chainName = "__default_seq_chain__"; 
var tInfos = asyncSeq.chainInfos = asyncSeq.chainInfos || {}; 
var tInfo = tInfos[chainName] = tInfos[chainName] || {count : 0, currentIndex : -1, abort : false}; 
for(var i = 0; i < funcArray.length; ++i) 
{ 
asyncWhile(function(item, tIndex){ 
return function(){ 
if( tInfo.abort ) 
return false; 
if( tInfo.currentIndex < tIndex ) 
return true; 
else if( tInfo.currentIndex == tIndex ) 
{ 
try{ 
item(); 
} 
catch(e){ 
if( abortWhenError ) tInfo.abort = true; 
} 
finally{ 
tInfo.currentIndex ++; 
} 
} 
else 
{ 
if( abortWhenError ) tInfo.abort = true; 
} 
return false; 
}; 
}(funcArray[i], tInfo.count ++)); 
} 
setTimeout(function(){ 
if( tInfo.count > 0 && tInfo.currentIndex == -1 ) 
tInfo.currentIndex = 0; 
},20); // 为了调试的原因,加了延迟启动 
}

由此,一个支持Copy&Paste的异步js函数库就完成了。具体的使用例子如下:
function testAsync() 
{ 
asyncSeq([function(){println("aSyncSeq -0 ");} 
, function(){println("aSyncSeq -1 ");} 
, function(){println("aSyncSeq -2 ");} 
, function(){println("aSyncSeq -3 ");} 
, function(){println("aSyncSeq -4 ");} 
, function(){println("aSyncSeq -5 ");} 
, function(){println("aSyncSeq -6 ");} 
, function(){println("aSyncSeq -7 ");} 
, function(){println("aSyncSeq -8 ");} 
, function(){println("aSyncSeq -9 ");} 
, function(){println("aSyncSeq -10 ");} 
, function(){println("aSyncSeq -11 ");} 
, function(){println("aSyncSeq -12 ");} 
, function(){println("aSyncSeq -13 ");} 
, function(){println("aSyncSeq -14 ");} 
, function(){println("aSyncSeq -15 ");} 
, function(){println("aSyncSeq -16 ");} 
, function(){println("aSyncSeq -17 ");} 
, function(){println("aSyncSeq -18 ");} 
, function(){println("aSyncSeq -19 ");} 
, function(){println("aSyncSeq -20 ");} 
, function(){println("aSyncSeq -21 ");} 
, function(){println("aSyncSeq -22 ");} 
, function(){println("aSyncSeq -23 ");} 
, function(){println("aSyncSeq -24 ");} 
, function(){println("aSyncSeq -25 ");} 
, function(){println("aSyncSeq -26 ");} 
, function(){println("aSyncSeq -27 ");} 
, function(){println("aSyncSeq -28 ");} 
, function(){println("aSyncSeq -29 ");} 
]); asyncSeq([function(){println("aSyncSeq test-chain -a0 ");} 
, function(){println("aSyncSeq test-chain -a1 ");} 
, function(){println("aSyncSeq test-chain -a2 ");} 
, function(){println("aSyncSeq test-chain -a3 ");} 
, function(){println("aSyncSeq test-chain -a4 ");} 
, function(){println("aSyncSeq test-chain -a5 ");} 
, function(){println("aSyncSeq test-chain -a6 ");} 
, function(){println("aSyncSeq test-chain -a7 ");} 
, function(){println("aSyncSeq test-chain -a8 ");} 
], "test-chain"); 
asyncSeq([function(){println("aSyncSeq -a0 ");} 
, function(){println("aSyncSeq -a1 ");} 
, function(){println("aSyncSeq -a2 ");} 
, function(){println("aSyncSeq -a3 ");} 
, function(){println("aSyncSeq -a4 ");} 
, function(){println("aSyncSeq -a5 ");} 
, function(){println("aSyncSeq -a6 ");} 
, function(){println("aSyncSeq -a7 ");} 
, function(){println("aSyncSeq -a8 ");} 
]); 
} 
var textArea = null; 
function println(text) 
{ 
if( textArea == null ) 
{ 
textArea = document.getElementById("text"); 
textArea.value = ""; 
} 
textArea.value = textArea.value + text + "\r\n"; 
}

最后,要向大家说一声抱歉,很多只想拿代码的朋友恐怕要失望了,如果你真的不知道怎么处理这些多余的行号,你可以学习一下正则表达式的替换,推荐用UltraEdit。
Javascript 相关文章推荐
JavaScript 新手24条实用建议[TUTS+]
Jun 21 Javascript
文本框输入时 实现自动提示(像百度、google一样)
Apr 05 Javascript
js控制frameSet示例
Sep 10 Javascript
Iframe实现跨浏览器自适应高度解决方法
Sep 02 Javascript
Jquery中Event对象属性小结
Feb 27 Javascript
jQuery文字横向滚动效果的实现代码
May 31 Javascript
基于jQuery下拉选择框插件支持单选多选功能代码
Jun 07 Javascript
JS实现仿饿了么在浏览器标签页失去焦点时网页Title改变
Jun 01 Javascript
js指定步长实现单方向匀速运动
Jul 17 Javascript
Vue2.0利用vue-resource上传文件到七牛的实例代码
Jul 28 Javascript
Rollup处理并打包JS文件项目实例代码
May 31 Javascript
jquery插件开发模式实例详解
Jul 20 jQuery
找出字符串中出现次数最多的字母和出现次数精简版
Nov 07 #Javascript
jquery 如何动态添加、删除class样式方法介绍
Nov 07 #Javascript
探索Emberjs制作一个简单的Todo应用
Nov 07 #Javascript
关于使用 jBox 对话框的提交不能弹出问题解决方法
Nov 07 #Javascript
seajs1.3.0源码解析之module依赖有序加载
Nov 07 #Javascript
Javascript引用指针使用介绍
Nov 07 #Javascript
JavaScript在多浏览器下for循环的使用方法
Nov 07 #Javascript
You might like
PHP中用header图片地址 简单隐藏图片源地址
2008/04/09 PHP
PHP错误Parse error: syntax error, unexpected end of file in test.php on line 12解决方法
2014/06/23 PHP
非常实用的php验证码类
2016/05/15 PHP
老生常谈PHP数组函数array_merge(必看篇)
2017/05/25 PHP
Laravel框架创建路由的方法详解
2019/09/04 PHP
PHP设计模式之 策略模式Strategy详解【对象行为型】
2020/05/01 PHP
准确获得页面、窗口高度及宽度的JS
2006/11/26 Javascript
js动态创建表格,删除行列的小例子
2013/07/20 Javascript
javaScript中的this示例学习详解及工作原理
2014/01/13 Javascript
javascript中createElement的两种创建方式
2015/05/14 Javascript
jQuery Real Person验证码插件防止表单自动提交
2015/11/06 Javascript
详解JavaScript语言的基本语法要求
2015/11/20 Javascript
js实现动态创建的元素绑定事件
2016/07/19 Javascript
JS实现列表页面隔行变色效果
2017/03/25 Javascript
react native与webview通信的示例代码
2017/09/25 Javascript
jQuery中 DOM节点操作方法大全
2017/10/12 jQuery
基于匀速运动的实例讲解(侧边栏,淡入淡出)
2017/10/17 Javascript
react+redux仿微信聊天界面
2019/06/21 Javascript
Vue开发环境中修改端口号的实现方法
2019/08/15 Javascript
Vue+abp微信扫码登录的实现代码示例
2020/01/06 Javascript
微信小程序 scroll-view的使用案例代码详解
2020/06/11 Javascript
教你使用python画一朵花送女朋友
2018/03/29 Python
在Python 字典中一键对应多个值的实例
2019/02/03 Python
Python骚操作之动态定义函数
2019/03/26 Python
python将字母转化为数字实例方法
2019/10/04 Python
Django 自动生成api接口文档教程
2019/11/19 Python
Python实现密钥密码(加解密)实例详解
2020/04/26 Python
Python参数传递实现过程及原理详解
2020/05/14 Python
英国在线购买轮胎、预订汽车、汽车维修和装配网站:Protyre
2020/04/12 全球购物
Ajax请求总共有多少种Callback
2016/07/17 面试题
授权委托书怎么写
2014/04/03 职场文书
社会治安综合治理管理责任书
2014/04/16 职场文书
公益广告标语
2014/06/19 职场文书
社区党员干部承诺书
2015/05/04 职场文书
商务英语邮件开头问候语
2015/11/10 职场文书
使用python绘制分组对比柱状图
2022/04/21 Python