zl程序教程

您现在的位置是:首页 >  前端

当前栏目

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

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

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

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

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

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

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

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

for(vari=0;i<dataArray.length;++i)
{
setTimeout(clickButtonForInputWindow);

setTimeout(waitInputWindowClose);
}

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

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

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

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

异步的判断:异步情况下的判断基本都是检测条件十分满足,然后执行某些动作。
异步的顺序:顺序中的每一步操作之后都要交回控制权,等待在下一个时间片中继续执行下一步。难点是如何保持顺序性。尤其在两个顺序动作中间夹杂一个异步的循环的时候。
异步的循环:每次循环之后都交回控制权到浏览器,如此循环,直到运行结束。
最简单的实现当然就是异步循环了,我的实现代码如下:
复制代码代码如下:
functionasyncWhile(fn,interval)
{
if(fn==null||(typeof(fn)!="string"&&typeof(fn)!="function"))
return;
varwrapper=function()
{
if((typeof(fn)=="function"?fn():eval(fn))!==false)
setTimeout(wrapper,interval==null?1:interval);
}
wrapper();
}

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

实际上,“等待并执行”逻辑,根本上就是一个异步循环问题。这种情况的实现方法示例如下:
复制代码代码如下:
asyncWhile(function(){
if(xxxCondition==false)
returntrue;//表示继续循环
else
doSomeThing();
returnfalse;//表示不需要继续循环了
});

对于非等待并执行的逻辑,简单一个setTimeout就可以了。
异步容易,实现异步中的顺序才叫难度呢。最早的起因是我要实现3步,但是第二部是一个异步的100多次的循环。也就是说,我要实现的3步操作,其实是103次的顺序异步操作。为了一个如何在浏览器中实现可响应的等待,找破了脑袋,只找到一个firefox中的实现,还要申请特权调用。
最后想出了一个简单的方法,就是引入了“执行链(ExecutionChain)”的概念,同一个执行链的所有登记函数是顺序的,不同执行链之间没有任何关系。另外,不提供互斥(mutex)等概念,如果要同步,自行在代码中检查。
在同一个执行链中,保存一个执行令牌,只有令牌和函数序号匹配,才允许执行,这样就保证了异步执行的顺序性。
复制代码代码如下:
functionasyncSeq(funcArray,chainName,abortWhenError)
{
if(typeof(funcArray)=="function")
returnasyncSeq([funcArray],chainName,abortWhenError);

if(funcArray==null||funcArray.length==0)
return;

if(chainName==null)chainName="__default_seq_chain__";
vartInfos=asyncSeq.chainInfos=asyncSeq.chainInfos||{};
vartInfo=tInfos[chainName]=tInfos[chainName]||{count:0,currentIndex:-1,abort:false};

for(vari=0;i<funcArray.length;++i)
{
asyncWhile(function(item,tIndex){
returnfunction(){
if(tInfo.abort)
returnfalse;
if(tInfo.currentIndex<tIndex)
returntrue;
elseif(tInfo.currentIndex==tIndex)
{
try{
item();
}
catch(e){
if(abortWhenError)tInfo.abort=true;
}
finally{
tInfo.currentIndex++;
}
}
else
{
if(abortWhenError)tInfo.abort=true;
}
returnfalse;
};
}(funcArray[i],tInfo.count++));
}

setTimeout(function(){
if(tInfo.count>0&&tInfo.currentIndex==-1)
tInfo.currentIndex=0;
},20);//为了调试的原因,加了延迟启动
}

由此,一个支持Copy&Paste的异步js函数库就完成了。具体的使用例子如下:
复制代码代码如下:
functiontestAsync()
{
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("aSyncSeqtest-chain-a0");}
,function(){println("aSyncSeqtest-chain-a1");}
,function(){println("aSyncSeqtest-chain-a2");}
,function(){println("aSyncSeqtest-chain-a3");}
,function(){println("aSyncSeqtest-chain-a4");}
,function(){println("aSyncSeqtest-chain-a5");}
,function(){println("aSyncSeqtest-chain-a6");}
,function(){println("aSyncSeqtest-chain-a7");}
,function(){println("aSyncSeqtest-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");}
]);
}

vartextArea=null;

functionprintln(text)
{
if(textArea==null)
{
textArea=document.getElementById("text");
textArea.value="";
}

textArea.value=textArea.value+text+"\r\n";
}

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