实现 Promise.race
基础知识
Promise.race
是 Promise
构造函数上的另一个重要的静态方法。如同其名 "race" (竞赛) 所示,它接收一个可迭代对象(通常是 Promise 数组)作为参数,然后返回一个新的 Promise
。这个新 Promise 的命运,取决于第一个“冲过终点”的 Promise。
要实现 Promise.race
,我们必须理解其核心的行为特性:
“率先出结果”:
Promise.race
会等待,直到传入的 Promise 中有任何一个率先进入“已敲定”(settled
)状态(无论是成功fulfilled
还是失败rejected
)。状态跟随领先者:
- 如果第一个敲定的 Promise 是成功的,那么
Promise.race
返回的新 Promise 也会立即成功,并且其resolve
的值就是那个率先成功的 Promise 的值。 - 如果第一个敲定的 Promise 是失败的,那么
Promise.race
返回的新 Promise 也会立即失败,并且其reject
的原因就是那个率先失败的 Promise 的原因。
- 如果第一个敲定的 Promise 是成功的,那么
结果一旦确定,后续不理: 一旦有一个 Promise 率先敲定了状态,
Promise.race
返回的 Promise 的状态就永远固定了。之后其他 Promise 的成功或失败都将被完全忽略。处理非 Promise 值: 如果传入的数组中包含非 Promise 的值(如数字、字符串),
Promise.race
会将其视为一个立即成功的 Promise。如果这个值是数组中的第一个元素,那么Promise.race
将立即以这个值成功返回。处理空数组: 如果传入一个空的可迭代对象,
Promise.race
返回的 Promise 将永远处于pending
状态,永远不会被 resolve 或 reject。这一点与Promise.all
不同。
核心思路
我们的目标是创建一个函数 myPromiseRace(promises)
,它能模拟原生 Promise.race
的行为。其核心思路比 Promise.all
更为简单,可以概括为“一次性触发”:
返回新 Promise: 函数必须返回一个新的
Promise
,以包裹整个“竞赛”过程。处理空数组: 首先处理边界情况,如果传入的数组为空,则返回一个永远
pending
的 Promise (即一个内部什么都不做的 Promise)。遍历输入: 立即遍历传入的
promises
数组,对每个元素进行处理。统一处理: 与
Promise.all
一样,使用Promise.resolve()
来包装数组中的每一个元素,确保我们后续处理的是一个标准的 Promise 对象。监听并“抢占”结果: 对每个包装后的 Promise 都调用
.then()
方法,并同时注册成功和失败的回调。- 无论是
onFulfilled
还是onRejected
被触发,都立即调用我们主 Promise 的resolve
或reject
方法。 - 因为 Promise 的状态一旦改变就无法再次更改,所以第一个执行
resolve
或reject
的回调将决定主 Promise 的最终命运。所有后续的回调执行都将无效。
- 无论是
代码实现
下面是一个遵循核心思路的 myPromiseRace
函数实现。
/**
* 自定义实现 Promise.race
* @param {Iterable<any>} promises - 一个可迭代对象,如 Promise 数组
* @returns {Promise<any>} - 返回一个新的 Promise
*/
function myPromiseRace(promises) {
// 1. 返回一个新的 Promise
return new Promise((resolve, reject) => {
// 2. 确保输入是数组(简化处理,原生支持所有可迭代对象)
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array.'));
}
// 3. 处理传入数组为空的边界情况,返回一个永远 pending 的 Promise
if (promises.length === 0) {
// 永远不调用 resolve 或 reject
return;
}
// 4. 遍历所有传入的 promise
promises.forEach((item) => {
// 5. 使用 Promise.resolve() 统一处理,兼容非 Promise 值
Promise.resolve(item).then(
// 6. 成功回调:只要有一个成功,主 Promise 就立即成功
(value) => {
resolve(value);
},
// 7. 失败回调:只要有一个失败,主 Promise 就立即失败
(reason) => {
reject(reason);
}
);
});
});
}
代码解析
- 返回 Promise:函数主体为
return new Promise(...)
,遵循标准结构,resolve
和reject
仅会被有效调用一次,确保 Promise 状态一旦改变便不可逆转。 - 参数校验:与
myPromiseAll
类似,先简单校验输入是否为数组,保证输入合法性。 - 空数组处理:当
promises.length === 0
时直接return
,使myPromiseRace([])
返回的 Promise 永远保持pending
状态,符合规范要求。 - 遍历任务数组:使用
forEach
遍历数组,无需关注index
和计数器,因只需确定“首个完成的任务”,不关心顺序。 - 统一转换为 Promise:通过
Promise.resolve(item)
处理每个输入项:- 若
item
是 Promise,直接返回该 Promise。 - 若为普通值,返回
Promise.resolve(item)
包装的立即成功 Promise,确保所有项均作为 Promise 处理。
- 若
- 成功回调处理:当任意子任务
item
率先成功时,其成功回调触发resolve(value)
,立即将主 Promise 状态置为fulfilled
,传入成功值value
,后续其他任务结果被忽略。 - 失败回调处理:当任意子任务
item
率先失败时,其失败回调触发reject(reason)
,立即将主 Promise 状态置为rejected
,传入失败原因reason
,后续其他任务结果被忽略。
核心逻辑理解
forEach
同步遍历为每个任务绑定 .then()
监听器,类似“终点裁判”。首个完成的任务(无论成功或失败)对应的回调会率先调用主 Promise 的 resolve/reject
,敲定主 Promise 状态,其余任务的回调不再生效,体现“赛跑”特性。