Promise.all、race、allSettled、any 有什么区别?
简化版
Promise.all 等全部成功或任一失败;race 谁先 settled 就跟谁;allSettled 等全部结束并保留每个结果;any 等任一成功,全部失败才失败。
详细版
四个组合方法适合不同并发场景:
Promise.all:多个请求都成功后再继续,适合强依赖并发。Promise.race:取最快完成的结果,常用于超时控制。Promise.allSettled:不因单个失败中断,适合批量任务汇总。Promise.any:只要一个成功就算成功,适合多源容灾。
关键差别在两个维度:等待到什么时候、失败如何传播。 面试时最好结合“3 个请求,1 个失败”的例子说明。
完整版教学
一、组合方法解决的是并发编排问题
单个 Promise 描述一个异步结果。 实际业务经常同时发起多个请求,例如用户信息、权限、配置、推荐列表。 组合方法的价值是把多个异步任务合成一个更高层的异步结果。
const p1 = fetchUser();
const p2 = fetchConfig();
const p3 = fetchPermissions();
const result = await Promise.all([p1, p2, p3]);
如果 3 个请求串行执行,每个 200ms,总耗时约 600ms。
如果并发执行并用 all 汇总,总耗时接近最慢的 200ms。
所以组合方法既是语义工具,也是性能工具。
二、Promise.all:全部成功才成功
Promise.all 接收一组 Promise。
只有所有成员都 fulfilled,整体才 fulfilled,结果数组顺序与输入顺序一致。
只要任意一个 rejected,整体立即 rejected。
const result = await Promise.all([
Promise.resolve('A'),
Promise.resolve('B')
]);
console.log(result); // ['A', 'B']
注意结果顺序不是完成顺序。 即使第二个请求先返回,结果仍放在索引 1。 这让调用方可以稳定地按输入位置解构结果。
p1 成功
p2 成功
p3 成功
-> all 成功:[v1, v2, v3]
p2 失败
-> all 失败:reason2
三、Promise.race:谁先结束就采用谁
race 的关键不是“谁先成功”,而是谁先 settled。
settled 包括 fulfilled 和 rejected。
因此最快失败的 Promise 也会让整体失败。
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error('timeout')), ms);
});
}
await Promise.race([fetchData(), timeout(3000)]);
这个例子用 3000ms 的定时器和真实请求比赛。
如果请求 1200ms 成功,整体成功。
如果请求 5000ms 才返回,超时 Promise 先 rejected,整体失败。
四、Promise.allSettled:关心每个任务的最终状态
allSettled 不会因为某一个任务失败就提前中断。
它会等所有任务都 settled,然后返回每个任务的状态和值或原因。
适合批量上传、批量通知、批量预加载等场景。
const results = await Promise.allSettled([
Promise.resolve(1),
Promise.reject(new Error('fail')),
]);
console.log(results);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: Error('fail') }
// ]
| 方法 | 成功条件 | 失败条件 | 是否保留全部结果 |
|---|---|---|---|
all | 全部成功 | 任一失败 | 失败时不保留 |
race | 最先成功 | 最先失败 | 只看第一个 settled |
allSettled | 全部结束 | 不会整体失败 | 保留 |
any | 任一成功 | 全部失败 | 成功时只取一个 |
五、Promise.any:多源容灾时很好用
any 等待第一个 fulfilled。
如果某个请求失败,它不会立刻让整体失败,而是继续等其他 Promise 成功。
只有全部 rejected,整体才 rejected,并抛出 AggregateError。
const fastest = await Promise.any([
fetchFromCDN1(),
fetchFromCDN2(),
fetchFromOrigin()
]);
假设 3 个源分别在 100ms 失败、180ms 成功、260ms 成功。
any 会在 180ms 成功返回第二个源的结果。
同样场景用 race,100ms 的失败会直接让整体失败。
六、失败传播和取消要分开理解
Promise.all 或 race 的整体状态确定后,并不代表其他异步任务被自动取消。
已经发出去的网络请求通常仍会继续执行。
如果需要真正取消,要结合 AbortController 或库提供的取消机制。
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal });
controller.abort();
记忆钩子:组合方法决定“等谁、信谁、怎么报结果”,不自动负责取消已经启动的副作用。
工程里如果 all 失败后不再需要其他请求结果,可以考虑主动取消。
否则可能浪费带宽,也可能产生意外的后续副作用。
七、常见误区与追问
- 误区:
race是谁先成功就返回谁。race看的是谁先 settled,最快失败也会让整体失败。 - 误区:
all失败会自动取消其他 Promise。 Promise 状态汇总不会自动取消底层任务,需要额外取消机制。 - 误区:
allSettled只适合失败场景。 它适合需要完整任务报告的场景,成功和失败都要统计。 - 追问:
all的结果顺序按完成顺序还是输入顺序? 按输入顺序,便于稳定解构和映射。 - 追问:
any全部失败时抛什么? 抛AggregateError,里面包含多个失败原因。 - 追问:请求超时用
race有什么坑?race只让外层 Promise 先结束,不会自动终止真实请求。
八、加强记忆
四个方法按口令记:all 要全成,race 抢第一个结束,allSettled 收全场报告,any 抢第一个成功。答题时再补两个关键细节:结果顺序按输入顺序,组合状态不等于底层任务自动取消。