实现 Promise.race

基础知识

Promise.racePromise 构造函数上的另一个重要的静态方法。如同其名 "race" (竞赛) 所示,它接收一个可迭代对象(通常是 Promise 数组)作为参数,然后返回一个新的 Promise。这个新 Promise 的命运,取决于第一个“冲过终点”的 Promise。

要实现 Promise.race,我们必须理解其核心的行为特性:

  1. “率先出结果”: Promise.race 会等待,直到传入的 Promise 中有任何一个率先进入“已敲定”(settled)状态(无论是成功 fulfilled 还是失败 rejected)。

  2. 状态跟随领先者:

    • 如果第一个敲定的 Promise 是成功的,那么 Promise.race 返回的新 Promise 也会立即成功,并且其 resolve 的值就是那个率先成功的 Promise 的值。
    • 如果第一个敲定的 Promise 是失败的,那么 Promise.race 返回的新 Promise 也会立即失败,并且其 reject 的原因就是那个率先失败的 Promise 的原因。
  3. 结果一旦确定,后续不理: 一旦有一个 Promise 率先敲定了状态,Promise.race 返回的 Promise 的状态就永远固定了。之后其他 Promise 的成功或失败都将被完全忽略。

  4. 处理非 Promise 值: 如果传入的数组中包含非 Promise 的值(如数字、字符串),Promise.race 会将其视为一个立即成功的 Promise。如果这个值是数组中的第一个元素,那么 Promise.race 将立即以这个值成功返回。

  5. 处理空数组: 如果传入一个空的可迭代对象,Promise.race 返回的 Promise 将永远处于 pending 状态,永远不会被 resolve 或 reject。这一点与 Promise.all 不同。

核心思路

我们的目标是创建一个函数 myPromiseRace(promises),它能模拟原生 Promise.race 的行为。其核心思路比 Promise.all 更为简单,可以概括为“一次性触发”

  1. 返回新 Promise: 函数必须返回一个新的 Promise,以包裹整个“竞赛”过程。

  2. 处理空数组: 首先处理边界情况,如果传入的数组为空,则返回一个永远 pending 的 Promise (即一个内部什么都不做的 Promise)。

  3. 遍历输入: 立即遍历传入的 promises 数组,对每个元素进行处理。

  4. 统一处理: 与 Promise.all 一样,使用 Promise.resolve() 来包装数组中的每一个元素,确保我们后续处理的是一个标准的 Promise 对象。

  5. 监听并“抢占”结果: 对每个包装后的 Promise 都调用 .then() 方法,并同时注册成功和失败的回调。

    • 无论是 onFulfilled 还是 onRejected 被触发,都立即调用我们主 Promise 的 resolvereject 方法。
    • 因为 Promise 的状态一旦改变就无法再次更改,所以第一个执行 resolvereject 的回调将决定主 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);
        }
      );
    });
  });
}

代码解析

  1. 返回 Promise:函数主体为 return new Promise(...),遵循标准结构,resolvereject 仅会被有效调用一次,确保 Promise 状态一旦改变便不可逆转。
  2. 参数校验:与 myPromiseAll 类似,先简单校验输入是否为数组,保证输入合法性。
  3. 空数组处理:当 promises.length === 0 时直接 return,使 myPromiseRace([]) 返回的 Promise 永远保持 pending 状态,符合规范要求。
  4. 遍历任务数组:使用 forEach 遍历数组,无需关注 index 和计数器,因只需确定“首个完成的任务”,不关心顺序。
  5. 统一转换为 Promise:通过 Promise.resolve(item) 处理每个输入项:
    • item 是 Promise,直接返回该 Promise。
    • 若为普通值,返回 Promise.resolve(item) 包装的立即成功 Promise,确保所有项均作为 Promise 处理。
  6. 成功回调处理:当任意子任务 item 率先成功时,其成功回调触发 resolve(value),立即将主 Promise 状态置为 fulfilled,传入成功值 value,后续其他任务结果被忽略。
  7. 失败回调处理:当任意子任务 item 率先失败时,其失败回调触发 reject(reason),立即将主 Promise 状态置为 rejected,传入失败原因 reason,后续其他任务结果被忽略。

核心逻辑理解

forEach 同步遍历为每个任务绑定 .then() 监听器,类似“终点裁判”。首个完成的任务(无论成功或失败)对应的回调会率先调用主 Promise 的 resolve/reject,敲定主 Promise 状态,其余任务的回调不再生效,体现“赛跑”特性。

Copyright © Jun 2025 all right reserved,powered by Gitbook该文件修订时间: 2025-07-03 17:35:08

results matching ""

    No results matching ""

    results matching ""

      No results matching ""