把网络限速到 3G 之后我才发现,那些通过了所有测试的代码其实到处是坑

远程办公技术宅 中级 2小时前 538 浏览 8 点赞 约 2 分钟

直接给结论:别总在光纤环境下测性能,尝试在 Chrome DevTools 里把网络切到 3G(现在 Chrome 把 Slow 3G 改名叫 3G 了,就在 Slow 4G 和 Offline 之间)。在这种环境下,很多平时被低延迟掩盖的「正确性 Bug」会直接现形,尤其是那些涉及异步请求的竞态条件。

把网络限速到 3G 之后我才发现,那些通过了所有测试的代码其实到处是坑

我之前在本地 WiFi 测的时候,App 运行得顺风顺水,结果一开 3G 限速,立马崩了两个地方。最恶心的是,这两个 Bug 所在的函数在我的单元测试里全部是 Pass 状态。

搜索结果顺序反了,这就是典型的竞态条件

最典型的一个坑出在搜索框上。代码写得极其简单,就是典型的 fetch 完直接 setResults

把网络限速到 3G 之后我才发现,那些通过了所有测试的代码其实到处是坑
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 毫秒。

把网络限速到 3G 之后我才发现,那些通过了所有测试的代码其实到处是坑

在关闭限速但保留服务端延迟的情况下,日志跑出来是这样的:

sent request for "apple"
sent request for "banana"
got response for "banana" after 210ms
got response for "apple" after 3001ms

结果就是:用户最后搜的是 banana,但页面最后刷出来的是 apple。如果开了 3G 限速,这种不确定性会更强,因为网络层会随机增加延迟,让这种顺序反转变得不可预测。

把网络限速到 3G 之后我才发现,那些通过了所有测试的代码其实到处是坑

这种 Bug 并不只出现在搜索框,只要一个组件在生命周期内发起了多次异步请求,且没有处理好响应顺序,就随时可能翻车。

怎么解决这种顺序错乱

解决这个问题的核心在于:不能信任响应到达的先后顺序,而要信任请求发出的顺序。

一个最简单的办法就是引入一个「请求 ID」或者使用 AbortController。在发起新请求之前,先把之前的请求给取消掉。这样即使旧请求的响应在后面才回来,它也已经被忽略了。

这次实测让我意识到,很多所谓的「性能优化」其实在面对真实网络环境时毫无意义,如果基础的正确性(Correctness)没保证,快慢已经不重要了。建议大家在提交 PR 前,强制自己用 3G 模式跑一遍核心链路,能抓到很多原本在测试环境下根本看不到的低级错误。

AI编程AI编程实战javascriptChrome DevToolsFetch API
同类方向的延伸案例可以参考AI大模型变现案例库,有不少直接可参考的案例。

全部回复 (4)

阿福在路上 高级 2小时前

这波操作太绝了,赶紧把 3G 模拟跑一遍,看看我那几个接口会不会直接崩掉...

0 回复
产品经理大熊 高级 2小时前

这种水帖发多了没意思,赶紧说说 4090 跑这个模型到底卡不卡。

0 回复
数据分析师小美 初级 2小时前

早知道这样我就不用在那个 500ms 延迟的垃圾接口上死磕半天了,你试过把缓存关掉再跑一次吗?

0 回复
阿Sam的日常 高级 2小时前

@数据分析师小美 关了缓存也没用吧?我看你这延迟得起码 2 秒才敢说死磕。

0 回复

发表回复

支持 Markdown 格式