JavaScript 同步与异步:从回调嵌套到信号量控制

作者:小括号时间:2026-10-29 17:14:08浏览:337

本文探讨 JavaScript 中同步与异步调用的核心差异,重点分析在 DWR 等 AJAX 框架下,如何因异步执行导致代码顺序混乱。通过对比 setTimeout/setInterval 与回调嵌套方案,指出深层嵌套的控制难题,并介绍使用信号量(Semaphore)机制实现异步调用同步化的工程实践,确保复杂业务逻辑的执行顺序。

JavaScript 同步与异步:从回调嵌套到信号量控制

本系列文章将不定期更新,内容涵盖 JavaScript 语言特性及其实际应用等多个维度。


除非另有特别说明,本文所有示例均基于浏览器环境运行。


本期主题聚焦于同步与异步执行机制的讨论。



在检索 JavaScript 相关信息时,常见的问题是探讨如何实现异步代码。


然而,本文关注的是相反的场景:如何让异步调用呈现出同步的效果,这听起来或许有些反直觉。



JavaScript 异步方法基础

首先解析 JavaScript 中的异步方法机制。


这是开发者常遇到的基础问题,实现方式相对简单,此处不再赘述。


以下展示两段相关代码示例。



setTimeout 方法允许在指定的毫秒数后执行指定函数,且仅执行一次。


例如:


alert(1);


setTimeout("alert(2)", 1000);


alert(3);


当代码执行至 setTimeout 时,主线程不会阻塞,而是继续执行后续代码(如 alert(3))。经过 1000 毫秒后,系统才会执行 alert(2)。


setInterval 方法则允许代码每隔指定时间执行一次指定方法,直到调用 clearInterval 停止。


例如:


alert(1);


timer = setInterval("alert(2)", 1000);


alert(3);


代码逻辑与前述类似,区别在于 alert(2) 会每隔 1000 毫秒重复执行,直至调用


clearInterval(timer);


需注意,setTimeout 和 setInterval 均为 window 对象的方法。


虽然可以直接调用,但规范写法应使用 window.setTimeout 和 window.setInterval。提及此细节是为了在后续 JavaScript 系列文章中深入讲解。



DWR 异步调用中的顺序问题

接下来讨论实际开发中遇到的问题。


当前使用 DWR 作为 AJAX 服务端引擎,调用 DWR 方法时需提供一个回调函数以接收服务端返回结果。


该回调函数的执行不会被阻塞,浏览器会启动独立线程进行处理。


这易于理解,因为 DWR 方法的执行时间不可预测。若此时调用被阻塞,而服务端处理耗时较长,浏览器将陷入假死状态,严重影响用户体验。


示例代码如下:



…

ServerHandler.getString("Weiming", function (str) { //"Weiming"是传回server的参数


alert(str);


}); // ServerHandler是dwr提供的server方法的interface,具体使用请参见dwr网站。


alert(1);


执行过程中,先执行 alert(1),随后在不可预测的时间点后执行 alert(str)。


对于简单的 Hello World 式调用,这通常不会引发问题。


但如果需要执行一系列有先后顺序的 DWR 函数,且后续执行依赖前面的返回结果,简单的代码书写顺序无法保证执行顺序。


var myID = null;


ServerHandler.getID(function (id) {


myID = id; //无法预料何时会执行这句话


});

ServerHandler.getUserWithID(myID, function (name) {


/*

此时myID还没有值,因为上面的 myID = id这段代码是需要一个时间段之后才会执行的


*/

alert("hello:" + name);


});

例如这样的代码就会出错。那么如何解决呢?


回调嵌套方案的局限性

最简单的实现方法是嵌套回调函数。


…

ServerHandler.getID(function (id) {


ServerHandler.getUserWithID(id, function (name) {


alert("hello:" + name);


}
});

这样我们就可以保证多个dwr方法调用的顺序了。这样貌似解决了问题。但是并不完美。


原因是当我们把JavaScript和Browser作为一个操作的平台和逻辑业务的平台(AJAX的应用程序,后面的JavaScript系列中会有提及),而不是一个简单的展示平台的时候。这样的回调函数嵌套就很难控制了。


这也就是我最开始指出的需要同步异步调用的一个方法。



基于信号量的同步解决方案

最终我在公司的解决方案是这样的。


写一个信号量的类(JavaScript的面向对象会稍后讲解),当我需要执行一个方法的时候,我就申请一部分信号量。


把需要被执行的方法放进信号量的队列进行等待。等前面等待的方法(如果存在)执行后在执行。


信号量将作为一个参数被传入执行的方法,这样这个方法可以决定释放这个信号量还是继续分发。


比如


var s = new Semaphore();


var myID = null;


s.p(function (e) { //把方法放入信号量队列


ServerHandler.getID(function (id) {


myID = id;


s.v(); //释放信号量


}
});

s.p(function (e) { //将第二个方法放到信号量队列,只有当前面的s.v()执行之后,这个方法才会执行。


ServerHandler.getName(myID, function (name) { //此时,可以保证myID一定有值


alert("Hello:" + name);


s.v();


})
})

这里只是对信号量这个方法进行了简单的阐述。


信号量还支持创建自信号量,如果创建了子信号量,那么父信号量必须等带所有的孩子都归还了信号量之后才可以执行他里面的代码。


由于代码的版权是公司的,所以很抱歉,现在无法给出相应的完整的信号量的实现。


如果下一段我有时间的话,我会给出一个我实现的版本的。

作者最新文章