异步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 style 中visibility和display之间的区别
Jan 22 Javascript
jquery eval解析JSON中的注意点介绍
Aug 23 Javascript
JavaScript极简入门教程(一):基础篇
Oct 25 Javascript
jquery禁止回车触发表单提交
Dec 12 Javascript
vue动态组件实现选项卡切换效果
Mar 08 Javascript
jQuery animate()实现背景色渐变效果的处理方法【使用jQuery.color.js插件】
Mar 15 Javascript
JavaScript实现简单的星星评分效果
May 18 Javascript
AngularJS 购物车全选/取消全选功能的实现方法
Aug 14 Javascript
AngularJS模糊查询功能实现代码(过滤内容下拉菜单排序过滤敏感字符验证判断后添加表格信息)
Oct 24 Javascript
elementui的默认样式修改方法
Feb 23 Javascript
echarts整合多个类似option的方法实例
Jul 10 Javascript
swiper.js插件实现pc端文本上下滑动功能示例
Dec 03 Javascript
找出字符串中出现次数最多的字母和出现次数精简版
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使用curl实现简单模拟提交表单功能
2017/05/15 PHP
关于Yii中模型场景的一些简单介绍
2019/09/22 PHP
PHP unset函数原理及使用方法解析
2020/08/14 PHP
javascript之解决IE下不渲染的bug
2007/06/29 Javascript
图片格式的JavaScript和CSS速查手册
2007/08/20 Javascript
提高网站性能之 如何对待JavaScript
2009/10/31 Javascript
博客侧边栏模块跟随滚动条滑动固定效果的实现方法(js+jquery等)
2013/03/24 Javascript
JS中window.open全屏命令解析及使用示例
2013/12/11 Javascript
分享28款免费实用的 JQuery 图片和内容滑块插件
2014/12/15 Javascript
JavaScript分析、压缩工具JavaScript Analyser
2014/12/31 Javascript
jquery文档操作wrap()方法实例简述
2015/01/10 Javascript
jQuery里filter()函数与find()函数用法分析
2015/06/24 Javascript
JS实现双击编辑可修改状态的方法
2015/08/14 Javascript
jQuery解析json格式数据简单实例
2016/01/22 Javascript
javascript的理解及经典案例分析
2016/05/20 Javascript
jQuery命名空间与闭包用法示例
2017/01/12 Javascript
「中高级前端面试」JavaScript手写代码无敌秘籍(推荐)
2019/04/08 Javascript
微信小程序绘制图片发送朋友圈
2019/07/25 Javascript
js简单实现自动生成表格功能示例
2020/06/02 Javascript
python自动化测试实例解析
2014/09/28 Python
Python中time模块与datetime模块在使用中的不同之处
2015/11/24 Python
Python对象转JSON字符串的方法
2016/04/27 Python
对python requests的content和text方法的区别详解
2018/10/11 Python
np.newaxis 实现为 numpy.ndarray(多维数组)增加一个轴
2019/11/30 Python
pytorch绘制并显示loss曲线和acc曲线,LeNet5识别图像准确率
2020/01/02 Python
python 爬虫之selenium可视化爬虫的实现
2020/12/04 Python
基于CSS3 animation动画属性实现轮播图效果
2017/09/12 HTML / CSS
利用HTML5+css3+jquery+weui实现仿微信聊天界面功能
2018/01/08 HTML / CSS
HTML最新标准HTML5总结(必看)
2016/06/13 HTML / CSS
使用phonegap创建联系人的实现方法
2017/03/30 HTML / CSS
英国剑桥包官网:The Cambridge Satchel Company
2016/08/01 全球购物
Java语言程序设计测试题判断题部分
2013/01/06 面试题
学校评语大全
2014/05/06 职场文书
不服从公司安排检讨书
2014/09/24 职场文书
2015年公司国庆放假通知
2015/07/30 职场文书
Java Redisson多策略注解限流
2022/09/23 Java/Android