网络限速到 3G 时,发现测试通过代码中的竞态 bug
在光纤网络下测试时,应用运行顺畅,但一旦通过 Chrome DevTools 将网络环境切换到 3G(当前 Chrome 已将 Slow 3G 改名为 3G,位置在 Slow 4G 和 Offline 之间),一些隐藏的正确性 bug 就会立马暴露出来,尤其是那些涉及异步请求的竞态条件。
我之前在本地 WiFi 环境下测试时,整个流程运行流畅无误,但当启用 3G 限速后,应用当场崩溃了两个地方。最令人头疼的是,这两个 bug 所在的函数在我的单元测试中完全通过。
搜索结果顺序反转——典型的竞态 bug
一个典型 bug 出现在搜索框中。实现逻辑非常简单,就是直接在 fetch 完成后立刻 setResults:
async function search(query) {
const res = await fetch(`/api/search?q=${query}`);
const data = await res.json();
setResults(data.results);
}
在高速网络下,请求和响应的时间差微乎其微,基本保持顺序感。但一旦网络变慢,情况就大不一样了。假设用户快速输入"apple",停顿一下,然后又赶紧改成"banana"。此时"apple"和"banana"的请求都已发出。如果"apple"的请求因为网络波动或服务器处理延迟,在"banana"响应后才到达,那么屏幕上最终显示的竟然是"apple"的结果。
为了在文章中复现这一现象,我特意在服务端做了手脚,给两个请求设置了不同的模拟延迟(注意,这是服务端延迟,不是网络限速):"apple" 设为 3 秒,"banana" 设为 200 毫秒。
关闭限速但保留服务端延迟时,日志输出如下:
sent request for "apple"
sent request for "banana"
got response for "banana" after 210ms
got response for "apple" after 3001ms
结果是:用户最后搜的是 banana,但页面最后刷出来的是 apple。如果启用了 3G 限速,这种不确定性会进一步加剧,因为网络层会随机增加延迟,让结果顺序反转变得不可预测。
这种 bug 并不仅限于搜索框,只要一个组件在生命周期内发起了多次异步请求,且没有妥善处理响应顺序,就随时可能翻车。
如何解决响应顺序错乱问题
解决这一问题的核心是:不能信任响应到达的先后顺序,而要信任请求发出的顺序。
最简单的办法就是引入一个「请求 ID」,或者使用 AbortController。在发起新请求前,先取消之前的请求。这样即使旧请求的响应在后面才回来,也会被忽略掉。
这次实测让我意识到,许多所谓的「性能优化」在真实网络环境面前其实毫无意义。如果基础的正确性(Correctness)得不到保障,快慢已经不重要了。建议大家在提交 PR 前,强制自己用 3G 模式跑一遍核心链路,这样就能发现很多原本在测试环境下完全看不见的低级错误。
全部回复 (4)
想当场把话说完?进全球 AI 聊天室,登录就能开口。
(切换网络环境至 3G,就像 Chrome DevTools 那样,把 Slow 4G 改名为 3G,位置在 Slow 4G 和 Offline 之间)我之前在本地 WiFi 环境下测试时,整个流程运行流畅无误,但当启用 3G 限速后,应用当场崩溃了两个地方。最令人头疼的是,这两个 bug 所在的函数在我的单元测试中完全通过。 ## 搜索结果顺序反转——典型的竞态 bug 一个典型 bug 出现在搜索框中。实现逻辑非常简单,就是直接在 fetch 完成后立刻 setResults:
async function search(query) { const res = await fetch(`/api/search?q=${query}`); const data = await res.json(); setResults(data.results); }
在高速网络下,请求和响应的时间差微乎其微,基本保持顺序感。但一旦网络变慢,情况就大不一样了。假设用户快速输入“apple”,停顿一下,然后又赶紧改成“banana”。此时“apple”和“banana”的请求都已发出。如果“apple”的请求因为网络波动或服务器处理延迟,在“banana”响应后才到达,那么屏幕上最终显示的竟然是“apple”的结果。 为了在文章中复现这一现象,我特意在服务端做了手脚,给两个请求设置了不同的模拟延迟(注意,这是服务端延迟,不是网络限速):“apple” 设为 3 秒,“banana” 设为 200 毫秒。 关闭限速但保留服务端延迟时,日志输出如下:
sent request for "apple" sent request for "banana" got response for "banana" after 210ms got response for "apple" after 3001ms
结果是:用户最后搜的是 banana,但页面最后刷出来的是 apple。如果启用了 3G 限速,这种不确定性会进一步加剧,因为网络层会随机增加延迟,让结果顺序反转变得不可预测。 这种 bug 并不仅限于搜索框,只要一个组件在生命周期内发起了多次异步请求,且没有妥善处理响应顺序,就随时可能翻车。 ## 如何解决响应顺序错乱问题 解决这一问题的核心是:不能信任响应到达的先后顺序,而要信任请求发出的顺序。 最简单的办法就是引入一个「请求 ID」,或者使用 AbortController。在发起新请求前,先取消之前的请求。这样即使旧请求的响应在后面才回来,也会被忽略掉。 这次实测让我意识到,许多所谓的「性能优化」在真实网络环境面前其实毫无意义。如果基础的正确性(Correctness)得不到保障,快慢已经不重要了。建议大家在提交 PR 前,

这波操作太绝了,赶紧在 Chrome DevTools 的 Network 里把网络环境切到 3G(就在 Slow 4G 和 Offline 之间),跑一遍看看我那几个接口会不会直接崩掉。