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

因为工作的需要,我要在网页端编写一段脚本,把数据通过网页批量提交到系统中去。所以我就想到了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。

(0)

相关推荐

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

    因为工作的需要,我要在网页端编写一段脚本,把数据通过网页批量提交到系统中去.所以我就想到了Greasemonkey插件,于是就开始动手写,发现问题解决得很顺利.但是在对脚本进行总结和整理的时候,我习惯性地问了自己一个问题:能不能再简单点? 我的答案当然是"能". 首先回顾我的数据批量提交的需求:我有一批用户数据要插入到系统中,但是因为系统库表结构不是行列式的,所以无法转化为sql语句插入.要插入的数据有接近200条,就是傻呵呵地手工录入到系统,估计也要1天的时间.作为程序员,当然不会干

  • jquery原理以及学习技巧介绍

    对于JQuery,想必大家都很熟悉.目前,很多web项目,在实施的过程中,考虑到各浏览器原生JS API的兼容性,大都会选用JQuery或类似于JQuery这样的框架来进行网页效果开发.JQuery上手简单,也很容易学,即使是刚接触JQuery的开发人员,借助JQuery手册,也很快能在项目中使用开发. 虽然JQuery相对简单,但要全面掌握,且快速灵活的使用它也并不那么容易,它提供了很多方法,包含了网页开发的各个知识面,所以要全面掌握这些知识点,个人认为还是需要对jquery有深入的理解,对这

  • 前端 JavaScript运行原理

    目录 1.什么是JavaScript引擎 2.V8引擎 3.运行时环境 4.运行时的调用栈 5.异步任务 6.总结 1.什么是JavaScript引擎 JavaScript引擎是一个计算机程序,它的主要作用是JavaScript运行时将源码编译为机器码. 每个主流Web浏览器都有自己的JavaScript引擎,它通常由web浏览器供应商开发. Google Chrome V8. Mozilla Firefox Spider Monkey. Safari Javascript Core Webki

  • JavaScript定时器原理

    目录 一. setTimeout() 定时器 二.停止 setTimeout() 定时器 三.setInterval() 定时器 四.清除setInterval() 定时器 五.电子时钟案例 前言: 在很多页面中,我们都可以看到一些倒计时或者和时间相关的效果,今天小熊将就JavaScript里面的倒计时做一概述. 首先,我们先来看看定时器,在JS中,有两种定时器: 一. setTimeout() 定时器 语法: window.setTimeout(调用函数, [延迟的毫秒数]); setTime

  • JavaScript定时器原理详解

    目录 一. setTimeout() 定时器 二.停止 setTimeout() 定时器 三.setInterval() 定时器 四.清除setInterval() 定时器 五.电子时钟案例 前言: 在很多页面中,我们都可以看到一些倒计时或者和时间相关的效果,今天小熊将就JavaScript里面的倒计时做一概述.首先,我们先来看看定时器,在JS中,有两种定时器: 一. setTimeout() 定时器 语法: window.setTimeout(调用函数, [延迟的毫秒数]); setTimeo

  • JavaScript闭包原理与用法实例分析

    本文实例讲述了JavaScript闭包原理与用法.分享给大家供大家参考,具体如下: 1.与闭包有关的两个概念: 1) 变量的作用域 不带有关键字var的变量会成为全局变量: 在函数中使用关键字var声明的变量是局部变量. 局部变量只有在函数内部才能访问到,在函数外面是访问不到的.但在函数内部可以通过作用域链一直向上搜索直到全局对象,也就是说,函数内部可以访问函数外部的变量. 2) 变量的生存周期 对于全局变量,其生存周期是永久的,除非主动销毁这个全局变量: 而对于在函数内用关键字var声明的局部

  • 详解Vue的异步更新实现原理

    最近面试总是会被问到这么一个问题:在使用vue的时候,将for循环中声明的变量i从1增加到100,然后将i展示到页面上,页面上的i是从1跳到100,还是会怎样?答案当然是只会显示100,并不会有跳转的过程. 怎么可以让页面上有从1到100显示的过程呢,就是用setTimeout或者Promise.then等方法去模拟. 讲道理,如果不在vue里,单独运行这段程序的话,输出一定是从1到100,但是为什么在vue中就不一样了呢? for(let i=1; i<=100; i++){ console.

  • JavaScript提升性能的常用技巧总结【经典】

    本文讲述了JavaScript提升性能的常用技巧.分享给大家供大家参考,具体如下: 1.注意作用域 随着作用域链中的作用域数量的增加,访问当前作用域以外的变量的时间也在增加.访问全局变量总是要比访问局部变量慢,因为要遍历作用域链.  1). 避免全局查找   将在一个函数中会多次用到的全局对象存储为局部变量总是没错的. 2). 避免 with 语句  with会创建自己的作用域,因此会增加其中执行代码的作用域链的长度. 2.选择正确的方法 性能问题的一部分是和用于解决问题的算法或者方法有关的.

  • JavaScript中数组常见操作技巧

    效果图如下所示: Tip: 右键在新标签中打开查看清晰大图 下面介绍JavaScript中的数组对象遍历.读写.排序等操作以及与数组相关的字符串处理操作 创建数组 一般使用数组字面量[]创建新数组,除非想要创建指定长度的数组 // good var arr = []; var arr = ['red', 'green', 'blue']; var arr = [ ['北京', 90], ['上海', 50], ['广州', 50] ]; // bad var arr = new Object()

  • Nodejs 和Session 原理及实战技巧小结

    一 Cookie 因为HTTP协议是没有状态的,但很多情况下是需要一些信息的,比如在用户登陆后.再次访问网站时,没法判断用户是否登陆过.于是就有了cookies,用于在浏览器端保存用户数据,它有如下特点 1 是在客户端浏览器端才有的 2 用于记录信息,大小最大为4K字节 3 如果使用了cookies,那么任何对该域名的访问都会带上cookies 目前新型网站更多的采用浏览器缓存,cookie会存在一些问题,比如你每次往服务器提交请求时,都会带上cookie,无论是你访问的是不是静态图片. coo

随机推荐