先记住这个答案
两者都是「谁快用谁」,区别在于怎么算快。Promise.race 看谁先结束,第一个结束的不管是成功还是失败,结果都直接采用。Promise.any 看谁先成功,中途失败的会被跳过,只要有一个成功就兑现;全部失败时才拒绝,拒绝值是 AggregateError。所以做超时控制用 race,从几个备用源里取一个能用的用 any。
race取第一个结束的,成功失败都算any取第一个成功的,失败直接跳过race适合做超时,any适合多节点择优- 传空数组:
race永远 pending,any立刻拒绝
同一组任务,两种结果
假设有两个节点:A 在 50 毫秒时失败,B 在 100 毫秒时成功。分别交给 race 和 any,看看拿到的是什么。
const delay = (ms, ok, v) =>
new Promise((resolve, reject) => setTimeout(() => (ok ? resolve(v) : reject(new Error(v))), ms))
// 每次调用都生成一组新任务:A 50ms 失败,B 100ms 成功
const tasks = () => [delay(50, false, '节点A失败'), delay(100, true, '节点B成功')]
Promise.race(tasks()).then(
(v) => console.log('race 成功:', v),
(e) => console.log('race 失败:', e.message) // A 先结束,直接失败
)
Promise.any(tasks()).then(
(v) => console.log('any 成功:', v), // 跳过 A,等到 B
(e) => console.log('any 失败:', e.message)
)查看输出与解释
race 失败: 节点A失败
any 成功: 节点B成功50 毫秒时 A 失败,race 立刻以这个错误拒绝,后面 B 成功了它也不管。any 把 A 的失败记下来继续等,100 毫秒时 B 成功,于是兑现为 B 的值。
可以这样记:race 是短跑比赛,第一个冲线的就是结果,哪怕他是摔着过线的;any 是找工作投简历,被拒几家没关系,拿到一个 offer 就算成功。
各自最适合的场景
两个 API 的差别决定了它们解决的是完全不同的问题,很少能互相替换。
race做超时控制- 把真实请求和一个定时拒绝的 Promise 放在一起比:
Promise.race([request, timeout(3000)]),其中timeout是自己用new Promise包setTimeout写的小函数,到点就reject。请求先回来就用请求的结果;3 秒还没回来,定时器先拒绝,整体就以超时错误失败。这里恰恰需要「失败也算结束」,换成any反而不行,超时的拒绝会被跳过,还得继续等请求。 如果请求本身支持signal,现在更推荐直接传AbortSignal.timeout(3000),超时后请求会被真正中断。 any做多节点择优- 同一份资源部署在几个 CDN 上,同时请求,用最先成功的那个。某个节点挂了、返回错误都不要紧,只要还有一个能用,用户就感知不到。这时如果用
race,一旦最快返回的恰好是一个错误,整个请求就失败了,兜底完全没起作用。
容易答错的地方
- 以为
race会自动取第一个成功的 race对成功和失败一视同仁。很多人用它做多源择优,结果某个节点秒回一个500,整个请求就失败了。要跳过失败,必须用any。- 以为结果定了,其他请求就被取消了
race和any只是不再关心剩下的结果,剩下的 Promise 该执行还执行,请求照样发完。要真正停掉,得给每个请求传signal,拿到结果后调用controller.abort()。- 忽略空数组的差别
Promise.race([])永远 pending,因为没有任何一个能结束。Promise.any([])立刻以AggregateError拒绝,因为不可能有成功。列表是动态拼出来的时候,这两种表现都可能让页面卡住或者报出奇怪的错。
面试官还会怎么问?
any 全部失败时,怎么拿到每个失败原因?
它拒绝的是一个 AggregateError,每个失败原因按输入顺序放在 err.errors 数组里。遍历这个数组就能知道每个节点分别是什么原因失败的。
手写一个 Promise.race 怎么写?
返回一个 new Promise,遍历输入,对每一项调用 Promise.resolve(item).then(resolve, reject)。谁先调用 resolve 或 reject 谁就生效,后面的调用会被忽略,因为 Promise 的状态只能改变一次。用 Promise.resolve 包一下是为了兼容数组里的普通值。
四个组合方法怎么一起记?
按「等不等全部」和「失败算不算」来分。Promise.all 等全部成功,有一个失败就失败。Promise.allSettled 等全部结束,永远不失败。Promise.race 取第一个结束的。Promise.any 取第一个成功的,全部失败才失败。all 和 any 正好是一对反义:一个是有一个失败就失败,一个是有一个成功就成功。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。