JavaScript 实现同步与异步混合的事件队列控制
本文介绍了一种基于 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 进行测试,结果显示目前看来并无问题。
D3.js 比例尺与坐标轴的配置详解
2026-11-05 16:54:46
JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19
