在前端面试和日常业务中,Promise.race 是一个非常常见但又容易被忽略的 API。它的规则很简单:
多个 Promise 同时“赛跑”,谁先落地(fulfilled 或 rejected),整体 Promise 就跟着落地。
这篇文章我们会:
- 先讲清楚
Promise.race的行为与典型使用场景 - 再手写一个
myPromiseRace - 最后补充边界情况、优化点与面试常考问题
1. Promise.race 是什么?
Promise.race(iterable) 接收一个可迭代对象(通常是数组),返回一个新的 Promise:
- 只要 任意一个 Promise 先 fulfilled,结果 Promise 就 fulfilled
- 只要 任意一个 Promise 先 rejected,结果 Promise 就 rejected
- 它不会等待全部结束,也不会“取消”其他 Promise(它们仍然会继续执行)
2. 常见使用场景
2.1 超时控制(最常见)
比如请求接口超过 3 秒就当作超时处理:
fetch(url)vstimeoutPromise(3000)- 谁先结束就用谁的结果
2.2 多源请求兜底
比如同一个资源从 CDN1/CDN2 同时拉取,谁先成功就用谁。
2.3 首屏竞速
首屏数据、降级数据、缓存数据同时读取,谁先返回就先渲染。
3. 手写 myPromiseRace
你这版实现非常接近原生行为:
- 校验 iterable
- 遍历输入
- 用
Promise.resolve兼容非 Promise 值 - 谁先 settle 就 resolve/reject
1 | function myPromiseRace(promises) { |
4. 关键点拆解(为什么要 Promise.resolve?)
Promise.race 的参数里不一定都是 Promise,可能是:
- 普通值:
Promise.race([1, 2, 3]) - thenable:
{ then(resolve) { resolve(123) } }
使用 Promise.resolve(x) 的好处是:
- 普通值会立刻变成 fulfilled Promise
- thenable 会被吸收并按 Promise 规则执行
这也是符合原生 Promise.race 的行为。
5. 更完整的版本:处理空 iterable
原生
Promise.race([])会返回一个永远 pending 的 Promise。
你现在的实现遇到空数组也会返回 pending(因为 for 循环不执行),这符合原生。
如果你想更明确一点,可以加个计数,但不是必须。
6. 更多测试用例
6.1 测试(先 reject)
1 | const p1 = new Promise(res => setTimeout(() => res('p1'), 1000)); |
6.2 普通值参与竞速(会立刻赢)
1 | myPromiseRace([Promise.resolve('A'), 123, new Promise(res => setTimeout(() => res('B'), 10))]) |
6.3 空数组:永远 pending
1 | const p = myPromiseRace([]); |
7. 实战:用 race 做请求超时
1 | function timeout(ms) { |
8. 面试常问点(加分项)
Q:race 能取消其他 Promise 吗?
不能。Promise 本身不支持取消,除非你用AbortController(fetch)或自己设计可取消任务。Q:race 里放普通值会怎样?
会被Promise.resolve包装成 fulfilled Promise,通常会立刻“获胜”。Q:race 和 any/allSettled 有什么区别?
race:谁先 settle 用谁any:谁先 fulfilled 用谁(全部 rejected 才 rejected)allSettled:等全部结束,返回每个结果状态
9. 总结
手写 Promise.race 的核心只有一句话:
遍历 iterable,把每个元素 Promise.resolve 后接上同一个 resolve/reject,谁先 settle 就定局。