在 JavaScript 的异步世界里,Promise 是处理异步操作的核心,而 Promise.race 作为 Promise 家族的重要成员,常被大家简单理解为 「谁先完成就返回谁」。但很多前端开发者只知其竞速本质,却忽略了它在实际业务中的隐藏神技 —— 从请求超时控制、资源加载优化,到中断异步操作、多源数据择优,Promise.race 能轻松解决大量前端异步痛点。
今天就带你彻底吃透 Promise.race,解锁它的「神奇用法」,让你的异步代码更优雅、更健壮!
一、先搞懂:Promise.race 基础原理
在学习高阶用法前,我们先回顾核心定义,避免基础误区:
语法:
Promise.race(iterable)参数:可迭代对象(数组、字符串等),内部是多个 Promise 实例
特性:
「竞速模式」:只要有一个 Promise 状态变更(成功 / 失败),race 就立刻返回结果
不等待其他 Promise:无论剩余异步任务是否完成,都直接忽略
结果跟随第一个变更的 Promise:第一个成功则整体成功,第一个失败则整体失败
最简示例
// 模拟两个异步请求
const p1 = new Promise(resolve => setTimeout(() => resolve('请求1完成'), 2000));
const p2 = new Promise(resolve => setTimeout(() => resolve('请求2完成'), 1000));
// race 竞速:谁快返回谁
Promise.race([p1, p2]).then(res => {
console.log(res); // 1秒后输出:请求2完成
});
这是 Promise.race 的基础用法,但这只是它的冰山一角。
二、Promise.race 五大神奇用法(前端实战必备)
用法 1:异步请求超时控制(最常用!)
业务场景:接口请求、图片加载、资源获取时,避免无限等待。比如用户网络差时,接口 3 秒没响应就提示超时,而非卡死页面。
这是 Promise.race 最经典的用法,没有之一。我们可以把「异步请求」和「超时定时器」放在一起竞速:
请求先完成 → 正常返回数据
定时器先完成 → 触发超时,终止等待
实战代码
/**
* 带超时控制的请求封装
* @param {Promise} request - 异步请求Promise
* @param {number} timeout - 超时时间(毫秒)
*/
function withTimeout(request, timeout) {
// 超时Promise:达到时间后直接reject
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('请求超时,请稍后重试'));
}, timeout);
});
// 竞速:请求 vs 超时
return Promise.race([request, timeoutPromise]);
}
// 使用示例:接口请求5秒超时
const fetchData = fetch('/api/userInfo');
withTimeout(fetchData, 5000)
.then(res => res.json())
.then(data => console.log('请求成功:', data))
.catch(err => console.error('错误:', err.message));
优势:无需手动清理定时器、无需复杂的状态判断,一行竞速搞定超时控制。
用法 2:多源数据择优加载(提升用户体验)
业务场景:同时请求主服务、备用服务、CDN 多个数据源,优先返回最快的可用数据,避免单节点故障导致页面空白。
比如电商商品详情、新闻数据,同时请求多个接口,谁快用谁。
实战代码
// 模拟三个数据源(主接口、备用接口、CDN接口)
const api1 = fetch('/api/main').then(res => res.json());
const api2 = fetch('/api/backup').then(res => res.json());
const api3 = fetch('/cdn/data').then(res => res.json());
// 竞速:返回最快的有效数据
Promise.race([api1, api2, api3])
.then(data => {
console.log('最快数据源返回:', data);
renderPage(data); // 渲染页面
})
.catch(err => console.log('所有请求均失败'));
优势:大幅降低接口响应时间,提升页面加载速度,增强服务容错性。
用法 3:可中断异步操作(优雅终止请求 / 定时器)
业务场景:组件卸载、用户切换页面时,中断未完成的异步请求、定时器,避免内存泄漏、冗余请求。
结合 AbortController(中断请求)+ Promise.race,可以实现可控的异步中断。
实战代码
// 创建中断控制器
const controller = new AbortController();
const signal = controller.signal;
// 1. 可中断的请求
const fetchData = fetch('/api/list', { signal });
// 2. 中断Promise:触发后直接reject,终止竞速
const abortPromise = new Promise((_, reject) => {
signal.addEventListener('abort', () => {
reject(new Error('异步操作已中断'));
});
});
// 竞速:请求 vs 中断信号
Promise.race([fetchData, abortPromise])
.then(res => res.json())
.catch(err => console.log(err.message));
// 触发中断(比如组件卸载时调用)
// controller.abort();
适用场景:React/Vue 组件卸载、单页页面切换、用户手动取消操作。
用法 4:图片 / 资源加载容错(避免页面占位空白)
业务场景:图片、JS/CSS 资源加载缓慢时,显示占位图、兜底图,提升视觉体验。
用 Promise.race 同时加载「目标图片」和「超时兜底」,图片加载慢就自动替换。
实战代码
/**
* 带兜底的图片加载
* @param {string} url - 目标图片地址
* @param {number} timeout - 加载超时时间
*/
function loadImageWithFallback(url, timeout) {
return new Promise((resolve) => {
const img = new Image();
// 图片加载成功
img.onload = () => resolve(url);
// 图片加载失败,直接返回兜底图
img.onerror = () => resolve('https://xxx.com/fallback.png');
img.src = url;
// 超时竞速:超时直接返回兜底图
const timeoutPromise = new Promise(resolve => {
setTimeout(() => resolve('https://xxx.com/fallback.png'), timeout);
});
return Promise.race([
new Promise(resolve => img.onload = () => resolve(url)),
timeoutPromise
]);
});
}
// 使用:图片3秒未加载完成,显示兜底图
loadImageWithFallback('https://xxx.com/cover.jpg', 3000)
.then(imgUrl => {
document.querySelector('img').src = imgUrl;
});
用法 5:批量异步任务「快速失败」
业务场景:多个依赖型异步任务,只要有一个失败,立即终止所有操作,无需等待全部完成。
比如表单提交前的多个校验(登录态、参数、权限),一个不通过直接拒绝提交。
实战代码
// 多个校验任务
const checkLogin = Promise.resolve('登录校验成功');
const checkParams = Promise.reject('参数格式错误'); // 这个任务先失败
const checkAuth = Promise.resolve('权限校验成功');
// 快速失败:一个失败,整体立即失败
Promise.race([checkLogin, checkParams, checkAuth])
.then(() => console.log('全部校验通过'))
.catch(err => {
console.log('校验失败:', err); // 直接输出:参数格式错误
// 终止表单提交
disableSubmit();
});
优势:避免无效等待,提升表单 / 流程的响应速度。
三、避坑指南:Promise.race 常见误区
误区 1:race 会取消其他未完成的 Promise
❌ 错误:
Promise.race只是不返回其他 Promise 的结果,不会自动中断异步任务(如请求、定时器)。✅ 解决方案:需要手动取消(如
AbortController、清除定时器)。误区 2:只处理成功,不处理失败
❌ 错误:如果第一个变更的 Promise 是失败状态,
race会直接抛出错误。✅ 解决方案:必须加 catch,捕获第一个失败的结果。
误区 3:传入空数组
❌ 错误:
Promise.race([])会一直处于 pending 状态,导致卡死。✅ 解决方案:保证传入的数组至少有一个 Promise。
四、总结:什么时候优先用 Promise.race?
一句话总结:只要你的业务需要「优先响应、超时控制、快速失败、择优加载」,首选 Promise.race。
它的核心价值不是「竞速」,而是 「异步流程的精准控制」,是前端处理异步问题的「瑞士军刀」。
高频使用场景速记
接口 / 资源超时控制
多源数据快速择优
可中断异步操作
图片 / 资源加载兜底
批量任务快速失败
吃透 Promise.race 这些神奇用法,你的前端异步代码会更简洁、更健壮,再也不用为异步等待、超时、冗余请求头疼!