😩前端必踩痛点:串行await拖慢页面加载
写前端接口联调时,很多新手会这么写: 多个无依赖接口,挨个await等待,第一个请求结束,才发第二个。 页面白屏时间直接翻倍,用户加载体验极差。
比如同时拉取语录+必应壁纸两个接口,串行写法要等两次请求总时长,并发只需最慢接口的耗时。
读完这篇你能学到:
- Promise三种状态流转与不可逆特性
- Promise.all核心执行逻辑、失败兜底规则
- 完整可运行前端并发请求实战代码
- Promise.all高频踩坑点与优化方案
- 串行/并发接口适用场景区分
一、先搞懂Promise基础状态,看懂并发底层逻辑
1. Promise三种固定状态
创建实例:new Promise(),初始状态为 pending(待处理)
最终只会两种终态,状态一旦确定,永久无法变更:
- fulfilled(成功):执行
.then(),拿到返回结果 - rejected(失败):执行
.catch(),捕获报错信息
2. Promise.all核心失败机制
接收Promise数组作为参数,执行规则:
- 所有请求全部成功,才会把结果按数组原始顺序返回
- 只要任意一个Promise失败,整体直接失败,立刻进入catch,不会等待其余未完成请求
二、实战场景:无依赖接口并发请求
需求:页面初始化同时拉取两条独立接口
- 一言语录接口:
https://v1.hitokoto.cn/?c=i&encode=json - 必应每日壁纸接口:
https://api.1314.cool/bingimg/?type=json&rand=1
两个接口无数据依赖,完全不需要串行等待,用Promise.all实现并发。
完整可运行HTML代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Promise.all并发请求实战</title>
</head>
<body>
<script>
// 封装两个独立接口请求函数
const getStory = async () =>
fetch('https://v1.hitokoto.cn/?c=i&encode=json')
const getRatp = async () =>
fetch('https://api.1314.cool/bingimg/?type=json&rand=1')
async function main() {
// 串行写法(低效,不推荐)
// const storyRes = await getStory();
// const imgRes = await getRatp();
// Promise.all 并发执行
Promise.all([getStory(), getRatp()])
.then(responseList => {
// responseList 顺序和传入Promise数组完全对应
// 批量解析所有接口返回的json数据
return Promise.all(responseList.map(res => res.json()))
})
.then(data => {
console.log('语录数据:', data[0])
console.log('壁纸数据:', data[1])
})
.catch(err => {
// 任意接口报错,直接进入这里
console.log('请求失败:', err)
})
}
// 执行并发请求
main();
</script>
</body>
</html>
代码核心逻辑拆解
- 把所有fetch Promise放进数组,传入
Promise.all,同时发起网络请求 - 接口返回的response数组,顺序永远和传入数组保持一致,不会因为接口响应快慢打乱顺序
- 全部接口请求完成后,统一调用
.map批量解析json() - 任意接口异常,直接触发catch终止流程
三、Promise.all三大高频踩坑提醒
坑1:单一接口报错,全部数据直接丢失
问题:多个接口并发,其中一个404/500,catch直接捕获,成功的接口数据也拿不到。 解决:每个Promise内部单独捕获异常,保证不中断整体流程:
const wrapFetch = (fn) => fn().catch(err => ({ error: true, msg: err }))
Promise.all([wrapFetch(getStory), wrapFetch(getRatp)])
坑2:混淆串行与并发,白白浪费加载时间
错误写法(串行,耗时叠加):
// 总耗时 = 接口1耗时 + 接口2耗时
const a = await getStory()
const b = await getRatp()
正确写法(并发,耗时取最大值):
// 总耗时 = 最慢接口耗时
const [a, b] = await Promise.all([getStory(), getRatp()])
坑3:忘记response.json()异步解析,直接打印response对象
fetch返回的是Response包装对象,不是业务数据,必须通过.json()二次解析,且该方法同样返回Promise,需要二次Promise.all批量处理。
四、什么时候用并发,什么时候只能串行?
✅ 优先使用Promise.all并发
- 接口之间无数据依赖,互不影响
- 页面初始化批量拉取字典、列表、配置类数据
- 批量上传、批量查询接口
❌ 必须串行await
- 第二个接口需要第一个接口返回的id/token作为参数
- 接口调用有严格先后业务流程(登录→获取用户信息)
五、总结
- Promise存在pending/fulfilled/rejected三种状态,状态切换不可逆;
- Promise.all实现无依赖接口并发,大幅缩短页面加载时长;
- 失败策略:任一Promise报错,整体直接失败,需单独兜底处理异常;
- 返回结果顺序固定,和传入Promise数组顺序一一对应,不受接口响应速度影响。
简单一句话:无依赖接口全用Promise.all并发,有数据依赖再用await串行。