JavaScript 实现同步与异步混合的事件队列控制

作者:异步漫步时间:2026-10-25 08:25:26浏览:415

本文介绍了一种基于 JavaScript 的事件队列实现方案,旨在解决异步操作导致的执行顺序混乱问题。通过维护同步与异步两个队列,利用状态机控制流程暂停与唤醒,实现了按添加顺序依次执行事件的功能。代码展示了如何手动管理异步回调的唤醒及延迟执行,适用于需要严格顺序控制的场景。

异步调用框架的痛点与背景

此前在网上阅读过一系列关于《写一个JavaScript异步调用框架1,2,3,4,5,6》的文章。



异步操作往往会导致事件触发的顺序不符合预期。过去遇到此类问题时,通常采用多层嵌套的方式(例如在 AJAX 返回后嵌套下一个事件)来强行解决。



仔细研读那系列文章后,感觉较为晦涩。不得不承认,这反映出我自身基础尚不扎实,对整体架构的把握也有欠缺。总体而言,我觉得理解难度较大,且其调用方式并不够便捷。



若采用如下调用方式:



var chain = Async.go(0);


chain


.next(function(){setTimeout("alert(1)",3000)})


.next(function(){setTimeout("alert(2)",3000)})


.next(function(){setTimeout("alert(3)",3000)});



这种写法看似完美。然而,若涉及实际的异步调用,则必须采用如下形式:



var chain = Async.go(0);


chain.next(function(){


var operation = new Async.Operation();


setTimeout(function(){ operation.yield("hello"); }, 3000);


return operation;


});

当然,文章最后一部分提到可以通过再次封装来缓解这种调用不便的问题。





自定义事件队列的设计思路

其实,我曾有过类似的想法,即寻找一个能按指定顺序触发事件的类或工具。在技术群中询问时,得到的回复往往是“在 onreadystatechange 里执行就好”,甚至伴有嘲讽。鉴于当时自己是初学者,便作罢了。如今虽仍自认菜鸟,但毕竟积累了一定经验,于是尝试按自己的理解编写一个事件队列。



我习惯以代码阐述观点,因此先展示代码,随后说明设计思路:


代码如下:

 
/** 
KEQueue —— Events Queue
@Author ake by 2010-04-25
http://www.cnblogs.com/akecn
@param data 事件队列中每个事件都会将该参数作为第一个参数传递下去,除非通过KEQueue.status修改它的值。
@method next(Function) 下一个要执行的事件。
@method wait(Number) 等待一定时间后执行下一个事件。
@method sleep() 停止事件序列的执行。
@method wake() 继续执行事件序列。
**/ 
var KEQueue = function(data) {
this.staticQueue = [];
this.asyncQueue = [];
this.status = "running";
this.result = data;
return this;
} 
KEQueue.prototype = {
next:function(callback, async) {//添加一个方法
if(!!async) {
this.staticQueue.push("async");//如果是异步方法(会有延时效果的方法)就添加标识
this.asyncQueue.push(callback);//延时方法的存放数组
}else {
this.staticQueue.push(callback);//直接触发的方法的存放数组
} 
return this;
}, 
wait:function(delay) {//延迟执行序列
var self = this;
this.next(function() {//模拟添加一个延时方法
setTimeout(function() {
self.wake.call(self)
}, delay);
},true);
return this;
}, 
go:function() {//按事件添加的先后顺序依次执行事件
if(this.staticQueue.length == 0) return;
while(this.staticQueue.length > 0) {
if(this.status === "sleep") return;
var fun = this.staticQueue.shift();
if(typeof fun == "string" && fun == "async") {
fun = this.asyncQueue.shift();
fun(this.result);
this.sleep();
}else {
fun(this.result);
} 
} 
}, 
sleep:function() {
this.status = "sleep";
}, 
wake:function() {
this.status = "running";
this.go();
} 
} 

核心实现逻辑与使用示例

阅读代码后,其实现原理应当一目了然,代码本身也较为简单。



核心逻辑即循环执行数组中的方法。若数组中存放的不是函数,则停止队列操作,直到被唤醒(调用 wake())。使用方法也偏向于个人偏好。



当然,我可能仅关注了事件按添加顺序执行这一表象,未考虑到其他复杂情况或原因。若您有建议或意见,欢迎留言交流!



以下是使用示例。


代码如下:

 
//示例1 添加事件、执行事件队列
function show(n) {
console.log(n);
} 
var o = new KEQueue("0");
o.next(function(d) { //参数是构造时传递的数据。整个事件队列都会返回该数据作为参数。
show(d + 1);
}).next(function(d) {
setTimeout(function() { //模拟延时操作(异步操作)
show(d + 2);
o.result = 0; //更改用以传递的数据,如果不修改,该数据会保持一致一直传递到最后一个事件。
o.wake(); //需要手动唤醒序列
},2000);
},true).next(function(d){
show(d + 3);
}).go();
o.next(function(d) {
setTimeout(function() {show(d + 4);o.wake(); },1000);
},true).wait(1000) //手动推迟1秒执行下面的方法
.next(function(d) {
show(d + 5);
}).go();
//示例2
o.next(function() {
show(1);
}) 
setTimeout(function() {
o.next(function(){
setTimeout(function() {
show(2);
o.wake();
},2000)
},true).go();
},1000);
setTimeout(function() {
o.next(function() {
show(3);
}).go();
},2000);

关于执行顺序与线程模型的思考

PS:夜间思考时突发奇想,若添加的是复杂事件,耗时较长,是否会导致非预期的事件顺序?若每个事件最终都需作为异步事件处理,则该队列意义有限,仅起到梳理事件顺序的作用。



次日清晨通勤途中又有所感悟:JavaScript 是单线程运行的,事件会被阻塞。若是多线程环境,大概也无需构建此类队列。



刚才编写 demo 进行测试,结果显示目前看来并无问题。

作者最新文章