本文分析JavaScript中异步Ajax请求因网络延迟导致回调执行顺序与发起顺序不一致的问题。通过示例展示直接Push数组造成的竞态条件Bug,并介绍利用URL判断索引和门控模式(Gate)两种解决方案,确保数据按预期顺序存入数组。
异步请求顺序错乱问题分析
案例分析:
在实现懒加载功能时,onscroll 事件可能被频繁触发,导致多次发起 Ajax 请求。由于网络响应时间不确定,各个请求的回调执行顺序往往与发起顺序不一致。这会导致最终通过 push 添加到数组中的数据顺序混乱,无法保证数据与请求的对应关系。
例子1:
var res = [];
function response(data) {
res.push( data );
}
// ajax(..)是某个库中提供的某个Ajax函数
ajax( "http://some.url.1", response );
ajax( "http://some.url.2", response );
在此场景中,并发处理的“进程”指的是两个处理 Ajax 响应的 response() 函数调用。它们的执行顺序是不确定的,可能以任意先后顺序完成。
我们期望的行为是:res[0] 存储调用 "http://some.url.1" 的结果,res[1] 存储调用 "http://some.url.2" 的结果。然而,实际结果取决于哪个请求先完成,有时顺序会相反。
这种执行顺序的不确定性极易引发竞态条件 bug,导致数据错乱。
基于URL索引的解决方案
解决办法
var res = [];
function response(data) {
if (data.url == "http://some.url.1") {
res[0] = data;
}
else if (data.url == "http://some.url.2") {
res[1] = data;
}
}
// ajax(..)是某个库中提供的某个Ajax函数
ajax( "http://some.url.1", response );
ajax( "http://some.url.2", response );
无论哪个 Ajax 响应先返回,我们都可以通过检查返回数据中的 data.url 字段(假设服务器始终返回该字段)来确定数据应存入 res 数组的具体位置。这样,res[0] 始终对应 "http://some.url.1" 的结果,res[1] 始终对应 "http://some.url.2" 的结果。通过这种简单的协调机制,即可避免由竞态条件引起的数据不确定性。
门控模式解决依赖问题
例子2:
var a, b;
function foo(x) {
a = x * 2;
baz();
}
function bar(y) {
b = y * 2;
baz();
}
function baz() {
console.log(a + b);
}
// ajax(..)是某个库中的某个Ajax函数
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
在该示例中,无论 foo() 还是 bar() 先被触发,都会导致 baz() 过早执行(此时变量 a 或 b 可能仍为 undefined);而第二次调用 baz() 时则正常,因为此时 a 和 b 均已赋值。
解决此问题有多种方法,这里提供一种简单的实现思路:
var a, b;
function foo(x) {
a = x * 2;
if (a && b) {
baz();
}
}
function bar(y) {
b = y * 2;
if (a && b) {
baz();
}
}
function baz() {
console.log( a + b );
}
// ajax(..)是某个库中的某个Ajax函数
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
在调用 baz() 前增加条件判断 if (a && b),这种传统上被称为“门”(gate)的模式。虽然我们无法确定 a 和 b 到达的顺序,但可以确保只有当两者都准备就绪后,才通过“开门”调用 baz()。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。