摘要
本文聚焦 Promise 核心并发控制方法,详细讲解 Promise.all、Promise.race、Promise.allSettled、Promise.any 的用法、区别、底层原理,并提供可直接用于面试的手写实现,结合接口并发请求、超时控制、容错处理等真实业务场景,帮你彻底掌握 Promise 并发编程,轻松应对前端面试与项目开发。
一、前言:为什么需要 Promise 并发控制?
在实际项目中,我们经常需要同时处理多个异步操作(如同时请求多个接口、并行获取多份数据),如果逐个等待异步操作完成,会严重影响页面性能和用户体验。
Promise 提供了 4 种核心并发控制方法,专门解决 "多异步并行执行" 的问题,也是前端面试的高频考点(手写 + 用法 + 区别,必问)。
核心需求:
- 多个异步操作同时执行,提高效率
- 控制多个异步的执行结果(全部成功、任意一个成功、全部结束、任意一个完成)
- 处理并发中的错误(部分失败、全部失败、超时失败)
二、Promise.all(最常用,全部成功才成功)
1. 核心定义
Promise.all (iterable):接收一个可迭代对象 (如数组),里面包含多个 Promise;只有所有 Promise 都成功(fulfilled) ,返回的新 Promise 才会成功,结果是所有 Promise 结果的数组;只要有一个失败(rejected),就会立即返回失败,失败原因是第一个失败的 Promise 的错误。
一句话记忆:"全部成功才成功,一个失败就失败"
2. 基础用法(实战必用)
javascript
运行
javascript
// 模拟3个接口请求(异步操作)
const request1 = () => Promise.resolve("接口1返回数据");
const request2 = () => Promise.resolve("接口2返回数据");
const request3 = () => Promise.resolve("接口3返回数据");
// 并发执行3个请求
Promise.all([request1(), request2(), request3()])
.then((res) => {
console.log("所有请求成功:", res); // [接口1数据, 接口2数据, 接口3数据]
})
.catch((err) => {
console.log("第一个失败的错误:", err);
});
// 测试失败场景(有一个请求失败)
const request4 = () => Promise.reject(new Error("接口4失败"));
Promise.all([request1(), request4(), request3()])
.then((res) => console.log(res))
.catch((err) => console.log(err.message)); // 接口4失败
3. 关键注意点(面试避坑)
- 传入的可迭代对象中,非 Promise 元素会被自动转为
Promise.resolve(元素)(如[1, 2, Promise.resolve(3)]会转为[Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]) - 结果数组的顺序和传入的 Promise 顺序一致(即使某个 Promise 先完成,也会按传入顺序排列结果)
- 一旦有一个 Promise 失败,会立即触发 catch,后续其他 Promise 的结果会被忽略
4. 手写 Promise.all(面试必写)
javascript
运行
javascript
// 手写 Promise.all
Promise.myAll = function (iterable) {
// 返回一个新 Promise
return new Promise((resolve, reject) => {
// 1. 处理边界:如果传入空数组,直接成功返回空数组
if (iterable.length === 0) {
resolve([]);
return;
}
const result = []; // 存储所有成功结果
let count = 0; // 记录已完成的 Promise 数量
// 2. 遍历所有可迭代对象中的元素
for (let i = 0; i < iterable.length; i++) {
// 处理非 Promise 元素,转为成功的 Promise
Promise.resolve(iterable[i])
.then((res) => {
result[i] = res; // 按顺序存储结果
count++;
// 3. 所有 Promise 都完成,resolve 结果数组
if (count === iterable.length) {
resolve(result);
}
})
.catch((err) => {
// 4. 有一个失败,立即 reject
reject(err);
});
}
});
};
// 测试手写 myAll
Promise.myAll([request1(), request2(), request3()])
.then((res) => console.log("手写myAll成功:", res))
.catch((err) => console.log(err));
三、Promise.race(最快完成的结果,无论成功失败)
1. 核心定义
Promise.race (iterable):接收一个可迭代对象,返回一个新 Promise;哪个 Promise 最先完成(无论成功还是失败),就返回哪个 Promise 的结果 / 错误。
一句话记忆:"比速度,谁先完成就取谁,不管成功失败"
2. 基础用法(实战场景:超时控制)
最常用场景:接口超时处理(如果接口在规定时间内未响应,就判定为失败)
javascript
运行
javascript
// 模拟接口请求(延迟3秒)
const request = () =>
new Promise((resolve) => setTimeout(() => resolve("接口请求成功"), 3000));
// 模拟超时(延迟2秒,返回失败)
const timeout = () =>
new Promise((_, reject) =>
setTimeout(() => reject(new Error("接口请求超时")), 2000)
);
// 用 race 实现超时控制:谁先完成取谁
Promise.race([request(), timeout()])
.then((res) => console.log(res))
.catch((err) => console.log(err.message)); // 接口请求超时(timeout先完成)
3. 关键注意点(面试避坑)
- 只要有一个 Promise 完成(fulfilled 或 rejected),就会立即返回结果,后续其他 Promise 的结果会被忽略
- 传入的非 Promise 元素,会被转为成功的 Promise,且会立即完成(因为同步元素,比异步 Promise 快)
- 常用于:超时控制、竞争条件(如多个请求取最快的一个)
4. 手写 Promise.race(面试必写)
javascript
运行
javascript
// 手写 Promise.race
Promise.myRace = function (iterable) {
return new Promise((resolve, reject) => {
// 遍历所有元素,谁先完成就 resolve/reject
for (const item of iterable) {
// 处理非 Promise 元素
Promise.resolve(item)
.then((res) => {
resolve(res); // 第一个成功,立即返回
})
.catch((err) => {
reject(err); // 第一个失败,立即返回
});
}
});
};
// 测试手写 myRace(超时场景)
Promise.myRace([request(), timeout()])
.then((res) => console.log(res))
.catch((err) => console.log("手写myRace失败:", err.message));
四、Promise.allSettled(所有操作都结束,无论成功失败)
1. 核心定义
Promise.allSettled (iterable):接收一个可迭代对象,返回一个新 Promise;只有所有 Promise 都结束(无论成功还是失败),才会返回成功,结果是一个数组,每个元素包含对应 Promise 的状态和结果 / 错误。
一句话记忆:"无论成功失败,都要等所有操作结束,返回所有结果"
2. 基础用法(实战场景:批量请求,需知道所有结果)
场景:批量获取多个接口数据,即使部分接口失败,也需要知道所有接口的执行结果(如批量导出、批量统计)
javascript
运行
javascript
const request1 = () => Promise.resolve("接口1成功");
const request2 = () => Promise.reject(new Error("接口2失败"));
const request3 = () => Promise.resolve("接口3成功");
// 等待所有请求结束,获取所有结果
Promise.allSettled([request1(), request2(), request3()])
.then((results) => {
console.log("所有请求结束:", results);
// 结果数组格式:
// [
// { status: 'fulfilled', value: '接口1成功' },
// { status: 'rejected', reason: Error: 接口2失败 },
// { status: 'fulfilled', value: '接口3成功' }
// ]
// 筛选成功/失败的请求
const success = results.filter((item) => item.status === "fulfilled");
const fail = results.filter((item) => item.status === "rejected");
console.log("成功的请求:", success);
console.log("失败的请求:", fail);
});
3. 关键注意点(面试避坑)
- 无论传入的 Promise 成功还是失败,返回的新 Promise 永远是 fulfilled 状态(除非传入的可迭代对象有异常)
- 结果数组中,每个元素只有两种格式:
- 成功:
{ status: 'fulfilled', value: 结果 } - 失败:
{ status: 'rejected', reason: 错误 }
- 成功:
- 和 Promise.all 的区别:all 有一个失败就立即失败,allSettled 会等待所有结束,返回所有结果
4. 手写 Promise.allSettled(面试加分)
javascript
运行
javascript
// 手写 Promise.allSettled
Promise.myAllSettled = function (iterable) {
return new Promise((resolve) => {
if (iterable.length === 0) {
resolve([]);
return;
}
const results = [];
let count = 0;
for (let i = 0; i < iterable.length; i++) {
Promise.resolve(iterable[i])
.then((value) => {
// 成功:存储状态和结果
results[i] = { status: "fulfilled", value };
})
.catch((reason) => {
// 失败:存储状态和错误
results[i] = { status: "rejected", reason };
})
.finally(() => {
count++;
// 所有操作结束,resolve 结果数组
if (count === iterable.length) {
resolve(results);
}
});
}
});
};
// 测试手写 myAllSettled
Promise.myAllSettled([request1(), request2(), request3()])
.then((results) => console.log("手写myAllSettled:", results));
五、Promise.any(任意一个成功就成功,全部失败才失败)
1. 核心定义
Promise.any (iterable):接收一个可迭代对象,返回一个新 Promise;只要有一个 Promise 成功(fulfilled) ,就立即返回成功,结果是第一个成功的 Promise 的结果;只有所有 Promise 都失败(rejected),才会返回失败,错误是所有失败原因的集合。
一句话记忆:"任意一个成功就成功,全部失败才失败"(和 all 相反,和 race 类似但只取成功的)
2. 基础用法(实战场景:多源请求容错)
场景:同一个接口,请求多个服务器(备用服务器),只要有一个服务器响应成功,就使用该结果,全部失败才提示错误
javascript
运行
javascript
// 模拟3个服务器请求(2个失败,1个成功)
const requestServer1 = () => Promise.reject(new Error("服务器1失败"));
const requestServer2 = () => Promise.resolve("服务器2请求成功");
const requestServer3 = () => Promise.reject(new Error("服务器3失败"));
// 只要有一个成功,就返回成功结果
Promise.any([requestServer1(), requestServer2(), requestServer3()])
.then((res) => console.log("请求成功:", res)) // 服务器2请求成功
.catch((err) => {
console.log("所有服务器都失败:", err.errors); // 所有失败原因的数组
});
// 测试全部失败场景
const requestA = () => Promise.reject(new Error("A失败"));
const requestB = () => Promise.reject(new Error("B失败"));
Promise.any([requestA(), requestB()])
.then((res) => console.log(res))
.catch((err) => console.log(err.errors)); // [Error: A失败, Error: B失败]
3. 关键注意点(面试避坑)
- 和 Promise.race 的区别:race 取 "最先完成" 的(无论成功失败),any 取 "最先成功" 的(失败的会忽略,直到有一个成功)
- 和 Promise.all 的区别:all 需全部成功,any 只需一个成功
- 若所有 Promise 都失败,返回的错误是
AggregateError类型,包含errors属性(所有失败原因的数组)
4. 手写 Promise.any(面试进阶)
javascript
运行
javascript
// 手写 Promise.any
Promise.myAny = function (iterable) {
return new Promise((resolve, reject) => {
if (iterable.length === 0) {
reject(new AggregateError([], "All promises were rejected"));
return;
}
const errors = []; // 存储所有失败原因
let count = 0;
for (let i = 0; i < iterable.length; i++) {
Promise.resolve(iterable[i])
.then((res) => {
// 第一个成功,立即 resolve
resolve(res);
})
.catch((err) => {
errors[i] = err;
count++;
// 所有都失败,reject AggregateError
if (count === iterable.length) {
reject(new AggregateError(errors, "All promises were rejected"));
}
});
}
});
};
// 测试手写 myAny
Promise.myAny([requestServer1(), requestServer2(), requestServer3()])
.then((res) => console.log("手写myAny成功:", res))
.catch((err) => console.log("手写myAny失败:", err.errors));
六、4 种并发方法对比(面试必背)
表格
| 方法 | 核心逻辑 | 成功条件 | 失败条件 | 结果格式 | 适用场景 |
|---|---|---|---|---|---|
| Promise.all | 全部完成 | 所有 Promise 成功 | 任意一个失败 | 成功结果数组(顺序和传入一致) | 多接口并行,需所有数据才渲染 |
| Promise.race | 最先完成 | 最先完成的 Promise 成功 | 最先完成的 Promise 失败 | 最先完成的结果 / 错误 | 超时控制、竞争条件 |
| Promise.allSettled | 全部完成 | 所有 Promise 都结束(无论成败) | 无(永远 fulfilled) | 每个元素包含 status 和 value/reason | 批量请求,需知道所有结果 |
| Promise.any | 任意一个成功 | 任意一个 Promise 成功 | 所有 Promise 都失败 | 第一个成功的结果 | 多源容错(备用服务器、备用接口) |
面试口诀(快速记忆)
- all:全成则成,一败则败
- race:谁快取谁,不分成败
- allSettled:尽收眼底,无论成败
- any:一成则成,全败则败
七、实战场景汇总(真实项目常用)
场景 1:多接口并行请求(all)
javascript
运行
javascript
// 同时请求用户、订单、商品数据,全部成功后渲染页面
const getUser = () => axios.get("/api/user");
const getOrder = () => axios.get("/api/order");
const getGoods = () => axios.get("/api/goods");
Promise.all([getUser(), getOrder(), getGoods()])
.then(([userRes, orderRes, goodsRes]) => {
// 解构结果,渲染页面
renderUser(userRes.data);
renderOrder(orderRes.data);
renderGoods(goodsRes.data);
})
.catch((err) => console.log("请求失败:", err));
场景 2:接口超时控制(race)
javascript
运行
javascript
// 封装带超时的请求函数
function requestWithTimeout(url, timeout = 3000) {
const request = axios.get(url);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error("超时")), timeout)
);
return Promise.race([request, timeoutPromise]);
}
// 使用
requestWithTimeout("/api/data")
.then((res) => console.log(res.data))
.catch((err) => console.log(err.message));
场景 3:批量导出(allSettled)
javascript
运行
javascript
// 批量导出多个表格数据,即使部分失败,也记录失败原因
const exportTable1 = () => Promise.resolve("表格1导出成功");
const exportTable2 = () => Promise.reject(new Error("表格2导出失败"));
const exportTable3 = () => Promise.resolve("表格3导出成功");
Promise.allSettled([exportTable1(), exportTable2(), exportTable3()])
.then((results) => {
results.forEach((item, index) => {
if (item.status === "fulfilled") {
console.log(`表格${index+1}:${item.value}`);
} else {
console.log(`表格${index+1}:${item.reason.message}`);
}
});
});
场景 4:多源请求容错(any)
javascript
运行
javascript
// 请求3个CDN资源,只要有一个成功就使用
const getCDN1 = () => axios.get("https://cdn1.com/resource");
const getCDN2 = () => axios.get("https://cdn2.com/resource");
const getCDN3 = () => axios.get("https://cdn3.com/resource");
Promise.any([getCDN1(), getCDN2(), getCDN3()])
.then((res) => console.log("使用资源:", res.data))
.catch((err) => console.log("所有CDN都失败:", err.errors));
八、高频面试题(必背标准答案)
-
**Promise.all、race、allSettled、any 的区别?**答:all 全成则成、一败则败;race 最先完成则返回(不分成败);allSettled 等待所有结束,返回所有结果;any 一成则成、全败则败。
-
**手写 Promise.all 的核心思路是什么?**答:遍历传入的可迭代对象,将非 Promise 转为成功 Promise,收集所有成功结果,当所有 Promise 完成时 resolve 结果数组,有一个失败则立即 reject。
-
**如何用 Promise.race 实现接口超时控制?**答:创建两个 Promise,一个是接口请求,一个是超时定时器(reject),用 race 包裹,谁先完成就取谁的结果,超时定时器先完成则判定为请求超时。
-
**Promise.allSettled 和 Promise.all 的区别?**答:all 有一个失败就立即失败,不等待其他 Promise;allSettled 会等待所有 Promise 结束,无论成败,返回所有结果,且永远是 fulfilled 状态。
-
**Promise.any 和 Promise.race 的区别?**答:race 取最先完成的(无论成败);any 只取最先成功的,失败的会忽略,直到有一个成功,全部失败才会 reject。
九、总结
- 4 种并发方法是 Promise 核心考点,也是项目中并行异步操作的必备工具;
- 重点掌握 each 方法的用法、区别和手写实现(all 和 race 是面试必写);
- 实战中根据需求选择对应方法:多接口需全部成功用 all,超时控制用 race,需所有结果用 allSettled,多源容错用 any;
- 理解每种方法的底层逻辑,能轻松应对面试手写和项目中的并发场景。