现在前端开发,只要写异步请求,基本人手一把 async/await。写法简洁、代码同步化、可读性高,几乎成了项目标配。
但很多同学只是“会用”,并不懂底层执行机制,日常开发中经常出现:接口串行卡死、报错直接白屏、并发请求冗余、try/catch 滥用等隐性问题。
这些 bug 往往不是必现问题,属于“偶发玄学 bug”,排查起来极其痛苦。
今天我总结日常工作中最容易踩、最容易被忽略的 5 个 async/await 实战深坑,全部都是业务真实踩坑,看完直接规避 90% 异步代码问题。
坑一:所有接口无脑串行,导致页面加载巨慢
很多新手写代码,习惯性一个 await 接一个 await:
const getInfo = async () => {
const user = await getUser()
const list = await getList()
const banner = await getBanner()
}
看起来没毛病,但是三个接口毫无依赖关系,却强行串行执行。
假设每个接口 300ms,串行总耗时接近 900ms,但其实完全可以并发执行。
正确写法:Promise.all 并发提速
const getInfo = async () => {
const [user, list, banner] = await Promise.all([
getUser(),
getList(),
getBanner()
])
}
总耗时直接缩短为最慢的那一个接口耗时,页面首屏速度大幅提升。
核心原则:无依赖接口一定并发,有依赖接口再串行。
坑二:Promise.all 一个报错,全部崩盘
Promise.all 有一个致命特性:只要一个请求报错,整体直接 reject,所有成功的数据全部拿不到。
业务场景中非常不合理:比如首页多个模块接口,一个模块挂了,不应该让整个首页白屏。
解决方案:使用 Promise.allSettled
const resList = await Promise.allSettled([
getUser(),
getList(),
getBanner()
])
返回结果会区分 fulfilled 成功和 rejected 失败状态,我们可以自行过滤有效数据,容错性拉满。
坑三:忘记 await,出现“异步幽灵 bug”
这是工作中出现频率最高、最难排查的问题。
代码看似没问题,但变量永远是 Promise 对象,拿不到真实数据:
const getData = async () => {
return await request.get('/api/list')
}
// 错误用法
const list = getData()
console.log(list) // Promise 对象
所有 async 函数返回一定是 Promise,无论内部有没有写 await。
上层调用必须加 await 或者 .then 取值,否则永远拿不到结果。
这种 bug 经常出现在:循环调用、事件回调、初始化函数中,极其隐蔽。
坑四:try/catch 乱包裹,报错范围过大
很多同学为了防止接口报错白屏,直接把所有逻辑全包进 try 里:
try {
const user = await getUser()
setUserName(user.name)
const list = await getList()
} catch (err) {
message.error('请求失败')
}
问题:
- 用户接口报错 → 列表接口直接不执行
- 代码逻辑卡死,无法局部降级
- 报错信息模糊,不知道哪个接口挂了
最佳实践:单个接口单独捕获异常
关键接口单独 try/catch,互不影响,支持局部降级,体验更好。
坑五:forEach 中使用 await,完全不生效
无数前端踩过这个经典大坑:
arr.forEach(async (item) => {
await request(item)
})
console.log('执行完成')
// 实际:不等异步结束就直接执行了
结论:forEach 不支持异步等待。
因为 forEach 内部不会等待 Promise 完成,会直接一次性全部执行完毕,导致异步逻辑错乱。
正确写法:使用 for 循环
for (let i = 0; i < arr.length; i++) {
await request(arr[i])
}
需要并发就配合Promise.all,稳定不出错。
总结
async/await 看着简单,实则非常讲究细节。很多线上卡顿、偶现 bug、数据错乱问题,根源都是以上几个不显眼的写法问题。
简单回顾最佳规范:
- 无依赖接口用 all 并发,提升速度
- 多模块请求用 allSettled 容错
- async 函数调用必加 await
- 精细 try/catch,不一把全包
- 异步遍历拒绝 forEach,优先 for 循环
如果你全部规避掉,你的异步代码质量直接超过大部分业务前端。