摘要本文聚焦 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运行// 模拟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运行// 手写 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运行// 模拟接口请求延迟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运行// 手写 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运行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运行// 手写 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运行// 模拟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运行// 手写 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多接口并行请求alljavascript运行// 同时请求用户、订单、商品数据全部成功后渲染页面 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接口超时控制racejavascript运行// 封装带超时的请求函数 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批量导出allSettledjavascript运行// 批量导出多个表格数据即使部分失败也记录失败原因 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(表格${index1}${item.value}); } else { console.log(表格${index1}${item.reason.message}); } }); });场景 4多源请求容错anyjavascript运行// 请求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 有一个失败就立即失败不等待其他 PromiseallSettled 会等待所有 Promise 结束无论成败返回所有结果且永远是 fulfilled 状态。Promise.any 和 Promise.race 的区别答race 取最先完成的无论成败any 只取最先成功的失败的会忽略直到有一个成功全部失败才会 reject。九、总结4 种并发方法是 Promise 核心考点也是项目中并行异步操作的必备工具重点掌握 each 方法的用法、区别和手写实现all 和 race 是面试必写实战中根据需求选择对应方法多接口需全部成功用 all超时控制用 race需所有结果用 allSettled多源容错用 any理解每种方法的底层逻辑能轻松应对面试手写和项目中的并发场景。