Promise.race() 实现“竞速降级”,即用空响应、真实请求和超时三者竞速,快者胜出,提升用户体验;空响应是立即 resolve 的轻量 Promise,非真实 HTTP 请求。

Promise.race() 本身不降级,但它能帮你“抢答”——在真实请求还没回来前,先用一个更快的兜底响应(比如空数据、缓存或简化结构)顶上,从而避免用户卡在 loading 或超时。所谓“竞速降级”,本质是让快的赢、慢的弃,不是等失败才降级,而是主动用速度换体验。
什么是“空请求”竞速?
“空请求”不是真的发空 HTTP,而是指一个立即 resolve 的 Promise,返回预设的轻量数据(如 { code: 0, data: [] } 或本地缓存快照)。它和真实 API 请求一起塞进 Promise.race(),谁先完成就用谁的结果。
适用场景:首页瀑布流、搜索建议、非核心列表等——数据稍旧/为空比卡住/报错更可接受。
基础实现:两路竞速 + 超时兜底
把真实请求、空响应、超时控制三者组合,形成三层保障:
前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。
立即学习“前端免费学习笔记(深入)”;
- 空响应 Promise:立刻返回精简结构(如空数组、默认头像、占位文案)
- 真实请求 Promise:正常调用后端接口
- 超时 Promise(可选):用
setTimeout拒绝,防止空响应“假赢”但用户等太久
示例代码:
const emptyFallback = () => Promise.resolve({
code: 0,
data: [],
message: '暂用缓存数据',
isFallback: true
});
const apiRequest = () => fetch('/api/list').then(r => r.json());
const timeout = (ms) => new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), ms)
);
// 竞速:空响应(快)、真实请求(准)、超时(保底线)
Promise.race([
emptyFallback(),
apiRequest(),
timeout(800)
])
.then(result => renderList(result))
.catch(err => {
if (err.message === 'timeout') {
showToast('网络较慢,展示本地缓存');
}
renderList(getLocalCache() || { data: [] });
});
高峰期动态策略:按服务状态切空响应质量
单纯固定空响应不够智能。可在请求前检查全局信号(如后端返回的 X-Rate-Limit-Remaining、本地计数器、或上游熔断开关),动态决定空响应内容:
- 流量正常 → 空响应返回「最近一次成功结果」(带时间戳校验)
- 接口延迟 > 500ms(来自历史统计)→ 返回「骨架数据」(如 3 条默认项 + loading 状态)
- 错误率 > 20% → 返回「静态提示页结构」+ 异步重试按钮
关键点:空响应逻辑要轻量、无副作用、可预测,不能触发二次请求或修改状态。
注意事项与避坑点
-
别忘了取消真实请求:Promise.race() 不会自动 abort Fetch 或 Axios 请求。需手动用
AbortController配合,否则竞速赢了,后台请求还在跑,浪费资源 - 空响应不是错误处理:它属于业务策略,应在 UI 层明确标识(如角标“数据已缓存”),避免用户误以为功能异常
- 避免嵌套 race:多层竞速易失控。建议单次 race 控制在 2–3 个分支,复杂策略用状态机或中间件封装
-
服务端配合更稳:理想情况是后端加
Prefer: handling=interactive头,主动返回轻量响应;前端 race 只是客户端兜底

















