在 JavaScript 异步编程中,Promise.all 和 Promise.race 是两个强大的组合工具,它们都能处理多个异步任务,但设计理念和使用场景截然不同。
本文将深入剖析两者的核心区别,并通过真实业务场景,告诉你“什么时候用哪个”。
一、核心概念对比
| 特性 | Promise.all | Promise.race |
|---|---|---|
| 中文含义 | “全部” | “竞速” |
| 成功条件 | 所有 Promise 都 fulfilled | 第一个 fulfilled 的 Promise |
| 失败条件 | 第一个 rejected 的 Promise | 第一个 rejected 的 Promise |
| 返回值 | 数组:按传入顺序排列的结果 | 单个值:最快完成的 Promise 的结果或错误 |
| 执行顺序 | 并发执行,不保证完成顺序 | 并发执行,谁快谁赢 |
| 典型用途 | 并行请求、数据聚合 | 超时控制、竞速加载 |
二、Promise.all:全都要,一个都不能少
✅ 核心逻辑
“等大家都到齐了,我们再开会。”
- 所有传入的 Promise 并发执行;
- 只有当所有都成功时,
Promise.all才 resolve; - 如果任一个失败,立即 reject,返回第一个失败的原因;
- 成功时返回数组,顺序与传入数组一致。
✅ 语法
Promise.all([promise1, promise2, promise3])
.then(results => {
// results 是数组,顺序固定
console.log(results); // [result1, result2, result3]
})
.catch(error => {
// 任一失败,立即捕获
console.error('失败原因:', error);
});
✅ 实战场景 1:并行加载多个资源
// 同时加载用户信息、文章列表、评论数据
Promise.all([
fetch('/api/user').then(res => res.json()),
fetch('/api/posts').then(res => res.json()),
fetch('/api/comments').then(res => res.json())
])
.then(([user, posts, comments]) => {
console.log('用户:', user);
console.log('文章:', posts);
console.log('评论:', comments);
// 渲染完整页面
})
.catch(err => {
console.error('任一请求失败,页面加载中断:', err);
});
✅ 优势:比串行快,且能保证所有数据就绪后再渲染。
✅ 实战场景 2:表单验证(所有字段必须通过)
const validateEmail = () => api.checkEmail('user@126.com');
const validatePhone = () => api.checkPhone('13800138000');
const validateUsername = () => api.checkUsername('john_doe');
Promise.all([
validateEmail(),
validatePhone(),
validateUsername()
])
.then(() => {
console.log('所有验证通过,可以提交!');
})
.catch(err => {
console.error('验证失败:', err.message); // 第一个失败的验证
});
✅ 注意:只要一个字段验证失败,整个提交就失败。
三、Promise.race:谁快用谁,落后淘汰
✅ 核心逻辑
“谁先冲过终点线,谁就是冠军。”
- 所有传入的 Promise 并发执行;
- 返回第一个完成的 Promise 的结果(无论成功或失败);
- 一旦有结果,其他 Promise 的结果被忽略。
✅ 语法
Promise.race([promise1, promise2, promise3])
.then(firstResult => {
console.log('最快的结果:', firstResult);
})
.catch(firstError => {
console.error('最快的错误:', firstError);
});
✅ 实战场景 1:设置请求超时
这是 Promise.race 最经典的用法!
// 创建一个超时 Promise
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`请求超时 (${ms}ms)`));
}, ms);
});
}
// 发起请求,并设置 5 秒超时
Promise.race([
fetch('/api/slow-data'), // 真实请求
timeout(5000) // 超时控制
])
.then(response => response.json())
.then(data => {
console.log('数据加载成功:', data);
})
.catch(err => {
console.error('请求失败:', err.message); // 可能是网络错误,也可能是超时
});
✅ 优势:避免用户无限等待,提升用户体验。
✅ 实战场景 2:竞速加载(CDN 切换)
从多个 CDN 加载同一资源,谁快用谁。
const cdnUrls = [
'https://cdn1.example.com/data.json',
'https://cdn2.example.com/data.json',
'https://cdn3.example.com/data.json'
];
const requests = cdnUrls.map(url =>
fetch(url).then(res => res.json())
);
Promise.race(requests)
.then(data => {
console.log('最快 CDN 返回数据:', data);
})
.catch(err => {
console.error('所有 CDN 都失败:', err);
});
✅ 优势:提升资源加载速度,增强系统健壮性。
✅ 实战场景 3:游戏中的“抢答”逻辑
// 模拟两个玩家抢答
const player1 = new Promise(resolve => {
setTimeout(() => resolve('玩家A抢答成功!'), 2000);
});
const player2 = new Promise(resolve => {
setTimeout(() => resolve('玩家B抢答成功!'), 1500);
});
Promise.race([player1, player2])
.then(winner => {
console.log(winner); // "玩家B抢答成功!"
});
四、关键差异详解
1️⃣ 成功返回值不同
const p1 = Promise.resolve('A');
const p2 = Promise.resolve('B');
// Promise.all 返回数组
Promise.all([p1, p2]).then(console.log); // ['A', 'B']
// Promise.race 返回单个值
Promise.race([p1, p2]).then(console.log); // 'A' (谁快谁先,这里几乎同时)
2️⃣ 失败行为不同
const success = Promise.resolve('Success');
const failFast = Promise.reject('Error 1');
const failSlow = new Promise((_, reject) =>
setTimeout(() => reject('Error 2'), 1000)
);
// Promise.all:第一个 reject 就失败
Promise.all([success, failFast, failSlow])
.catch(console.log); // 输出: Error 1
// Promise.race:第一个完成的 Promise 决定结果
Promise.race([success, failFast, failSlow])
.catch(console.log); // 输出: Error 1 (failFast 最快)
3️⃣ 顺序保证不同
const slow = new Promise(r => setTimeout(() => r('慢'), 2000));
const fast = new Promise(r => setTimeout(() => r('快'), 1000));
Promise.all([slow, fast]).then(console.log);
// 输出: ['慢', '快'] → 顺序按传入数组,不是完成顺序
Promise.race([slow, fast]).then(console.log);
// 输出: '快' → 谁快谁赢
五、如何选择?决策树
graph TD
A[需要处理多个异步任务?] --> B{是否必须全部成功?}
B -->|是| C[使用 Promise.all]
B -->|否| D{是否只关心最快的结果?}
D -->|是| E[使用 Promise.race]
D -->|否| F[考虑 Promise.allSettled 或 Promise.any]
六、注意事项
⚠️ Promise.all 的“短路”特性
一旦有一个 Promise reject,Promise.all 立即失败,后续 Promise 仍会执行,但结果被忽略。
const p1 = Promise.reject('Fail');
const p2 = new Promise(r => {
console.log('p2 仍在执行...'); // 会打印
setTimeout(() => r('Done'), 1000);
});
Promise.all([p1, p2]).catch(() => {}); // p2 依然执行
🔧 如果你想等所有结果(无论成败),用
Promise.allSettled。
💡 结语
“
Promise.all是团队协作,Promise.race是个人竞速。”
| 用法 | 记忆口诀 |
|---|---|
Promise.all | “全勤奖” —— 大家都到才发奖 |
Promise.race | “冠军赛” —— 第一名说了算 |
掌握它们的区别,你就能:
- 用
all实现高效并行; - 用
race实现超时控制和竞速加载。