5个真实场景解析:Promise.all、race、any的核心差异与选型指南
商品详情页的加载进度条突然卡在80%,后台同时发起的三个API请求中有一个查询物流信息的接口响应缓慢,整个页面陷入等待。这正是去年我们团队优化电商平台时遇到的实际问题——当多个异步操作并行时,如何智能控制流程?本文将用五个真实开发场景,带你掌握Promise高阶方法的选型逻辑。
1. 并发控制基础:重新理解Promise三剑客
在商品详情页的典型加载流程中,通常需要并行获取商品基本信息、库存状态和用户评价数据。假设我们用三个独立的Promise来表示这些异步请求:
const productInfo = fetch('/api/product/123'); const stockStatus = fetch('/api/inventory/123'); const userReviews = fetch('/api/reviews/123');Promise.all的工作机制就像严格的团队领导:
- 必须所有成员都成功报告(resolve),才会汇总结果
- 任一成员失败(reject)立即终止整个流程
- 结果数组顺序与输入Promise顺序严格对应
// 商品页完整数据加载 Promise.all([productInfo, stockStatus, userReviews]) .then(([info, stock, reviews]) => { renderProductPage(info, stock, reviews); }) .catch(err => { showErrorToast('部分数据加载失败'); });Promise.race的行为像体育比赛的终点摄像机:
- 只捕捉第一个冲过终点线的选手(无论成功失败)
- 其他选手仍在继续比赛但结果被忽略
- 典型场景:请求超时控制
// 请求超时处理 const timeout = new Promise((_, reject) => { setTimeout(() => reject(new Error('请求超时')), 5000); }); Promise.race([productInfo, timeout]) .then(info => { renderBasicProductInfo(info); }) .catch(() => { showNetworkWarning(); });Promise.any则像乐观的投资者:
- 只要有一个成功案例就视为整体成功
- 全部失败时才认为投资失败
- 浏览器兼容性提示:需要Chrome 85+或Polyfill
| 方法 | 成功条件 | 失败条件 | 结果特征 |
|---|---|---|---|
| Promise.all | 全部成功 | 任一失败 | 数组按输入顺序排列 |
| Promise.race | 第一个完成 | 第一个完成且为拒绝 | 单个最快结果 |
| Promise.any | 任一成功 | 全部失败 | 单个最先成功结果 |
2. 全有或全无:Promise.all的严谨之道
在订单结算页面的场景中,我们需要同时验证库存、优惠券和配送地址三项信息,任何一项不满足条件都应终止结算流程。这正是Promise.all的完美应用场景:
async function validateCheckout() { try { const [isStockValid, isCouponValid, isAddressValid] = await Promise.all([ checkInventory(order.items), verifyCoupon(order.couponCode), validateAddress(order.shippingAddress) ]); if (isStockValid && isCouponValid && isAddressValid) { proceedToPayment(); } } catch (error) { showCheckoutError(error.message); } }错误处理的高级技巧:
- 配合async/await使用try-catch结构更清晰
- 在catch块中可通过error对象分析具体失败原因
- 重要提示:即使有Promise被reject,其他Promise仍会继续执行
实际项目中,建议为每个Promise添加独立的catch处理,避免全局catch后丢失错误详情
批量文件上传是另一个典型用例。当需要确保所有文件都成功上传后才能提交表单时:
const uploadTasks = selectedFiles.map(file => uploadFile(file).catch(err => { // 为每个上传任务单独处理错误 console.error(`文件${file.name}上传失败`, err); throw err; // 重新抛出以触发Promise.all的catch }) ); Promise.all(uploadTasks) .then(() => { submitForm(); }) .catch(() => { alert('部分文件上传失败,请检查后重试'); });3. 竞速场景:Promise.race的极速哲学
在电商平台的CDN资源加载优化中,我们同时从多个镜像源请求同一资源,采用最先响应的结果:
const cdnSources = [ 'https://cdn1.example.com/static/main.js', 'https://cdn2.example.com/static/main.js', 'https://cdn3.example.com/static/main.js' ]; const resourceRequests = cdnSources.map(url => fetch(url).then(res => { // 取消其他未完成的请求 resourceRequests.forEach(req => req.abort?.()); return res.blob(); }) ); Promise.race(resourceRequests) .then(script => { loadScript(script); }) .catch(() => { fallbackToLocalScript(); });竞速模式的高级应用:
- 超时控制:包装原始Promise与定时reject的Promise
- 降级策略:优先尝试现代API,超时后回退传统方案
- 性能监控:记录各源的响应时间差异
// 带超时的API请求 function fetchWithTimeout(url, timeout = 3000) { const timeoutReject = new Promise((_, reject) => { setTimeout(() => reject(new Error('请求超时')), timeout); }); return Promise.race([ fetch(url), timeoutReject ]); }一个常见的误区是认为Promise.race会取消其他Promise的执行。实际上,未被采用的Promise仍会继续执行直到完成,只是它们的结果被忽略了。在需要真正取消操作的场景,需要配合AbortController等机制。
4. 宽容策略:Promise.any的弹性思维
当我们的应用需要从多个备用数据源获取信息时,Promise.any提供了完美的解决方案。例如在天气预报应用中:
const weatherSources = [ fetch('https://api.weather.com/v1'), fetch('https://backup.weather-api.com/v2'), fetch('https://community-driven-weather.org/api') ]; Promise.any(weatherSources) .then(weatherData => { updateWeatherDisplay(weatherData); }) .catch(() => { showError('所有数据源均不可用'); });与race的关键区别:
- race接受第一个完成的结果(无论成功失败)
- any只接受第一个成功的结果,忽略所有拒绝直到全部失败
- 错误处理:any在所有输入都拒绝时抛出AggregateError
// 错误处理示例 Promise.any([ Promise.reject('错误1'), Promise.reject('错误2') ]).catch(err => { console.log(err.errors); // ['错误1', '错误2'] });在服务端渲染(SSR)场景中,我们可以利用Promise.any实现多级缓存策略:
async function renderWithCache(page) { try { // 尝试从内存缓存、Redis缓存和数据库依次获取 const html = await Promise.any([ memoryCache.get(page), redisClient.get(`page:${page}`), database.query('SELECT content FROM pages WHERE name = ?', [page]) ]); return html; } catch { return generateFreshContent(page); } }5. 综合实战:电商平台的全流程优化
让我们回到最初的商品详情页案例,通过组合不同的Promise策略实现分级加载:
async function loadProductPage(productId) { // 核心数据:使用all保证完整性 const [basicInfo] = await Promise.all([ fetch(`/api/products/${productId}`), // 其他必要数据... ]); // 次要数据:使用any提升用户体验 const recommendations = await Promise.any([ fetch(`/api/recommendations/${productId}`), getCachedRecommendations(productId), getGenericRecommendations() ]).catch(() => []); // 竞品价格:使用race设置超时 const competitorPrices = await Promise.race([ fetchCompetitorPrices(productId), timeout(2000).then(() => []) ]); return { basicInfo, recommendations, competitorPrices }; }性能优化指标对比:
| 加载策略 | 成功条件 | 平均耗时 | 数据完整性 |
|---|---|---|---|
| 纯Promise.all | 全部成功 | 3200ms | 100% |
| 混合策略 | 核心数据完整 | 1800ms | 95% |
| 纯Promise.race | 第一个响应 | 900ms | 65% |
在错误恢复方面,我们实现了三级回退机制:
- 主API失败时尝试备用API
- 备用API不可用时读取本地缓存
- 缓存未命中返回精简数据模板
async function getProductDetails(productId) { try { return await Promise.any([ fetchMainAPI(productId), fetchBackupAPI(productId), getFromCache(productId) ]); } catch { return getFallbackTemplate(); } }