如何实现一个 Promise.all 或 Promise.race 函数?

嘿,大家好!我是你们今天的 Promise 解说员。今天咱们来聊聊 Promise 的两个重量级选手:Promise.allPromise.race。这两个家伙在处理多个 Promise 时,能发挥巨大的作用。别害怕,我会用最简单、最幽默的方式,带你彻底搞懂它们,并且手写实现它们!

开场:Promise 的小秘密

在深入 Promise.allPromise.race 之前,先简单回顾一下 Promise 的基本概念。Promise 代表一个异步操作的最终完成 (或失败) 及其结果值。 它有三种状态:

  • pending (进行中):初始状态,既没有被兑现,也没有被拒绝。
  • fulfilled (已兑现):操作成功完成。
  • rejected (已拒绝):操作失败。

第一幕:Promise.all – 团队协作的力量

Promise.all 就像一个团队的队长,它会等待所有队员(Promise)都完成任务,然后才会宣布整个团队任务完成。如果其中任何一个队员失败了,队长就会直接宣布整个团队任务失败。

Promise.all 的规则:

  • 输入: 接收一个 Promise 数组(或者任何 iterable 对象,比如 Set, Map 等,只要里面的元素都是 Promise-like 对象)。
  • 输出: 返回一个新的 Promise。
  • 成功: 当输入的所有 Promise 都成功 fulfilled 时,返回的 Promise 也会被 fulfilled,并且它的 value 是一个包含所有 Promise 的 fulfillment 值的数组,数组的顺序和输入 Promise 的顺序一致。
  • 失败: 只要输入中的任何一个 Promise 被 rejected,返回的 Promise 就会立即被 rejected,并且它的 reason 就是第一个被 rejected 的 Promise 的 reason。

Promise.all 的使用场景:

  • 并行请求: 同时发起多个 API 请求,等待所有请求都完成后,再进行下一步操作。
  • 资源加载: 同时加载多个资源文件(例如图片、脚本、样式),等待所有资源都加载完成后,再渲染页面。

手写实现 Promise.all

现在,让我们撸起袖子,手写一个 Promise.all。 这次咱们用 ES6 的语法,让代码更简洁易懂。

function myPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    // 1. 参数校验:确保传入的是一个可迭代对象
    if (!promises || typeof promises[Symbol.iterator] !== 'function') {
      return reject(new TypeError('Argument is not iterable'));
    }

    // 2. 将可迭代对象转换为数组 (防止传入的是类数组对象)
    const promiseArray = Array.from(promises);

    // 3. 如果传入的是空数组,则立即 resolve 一个空数组
    if (promiseArray.length === 0) {
      return resolve([]);
    }

    // 4. 用于存储结果的数组
    const results = [];
    // 5. 用于记录已完成的 Promise 的数量
    let completedCount = 0;

    // 6. 遍历 Promise 数组
    for (let i = 0; i < promiseArray.length; i++) {
      const promise = promiseArray[i];

      // 7. 确保数组中的每一项都是一个 Promise 对象,如果不是,则将其包装成 Promise
      Promise.resolve(promise).then(
        (value) => {
          // 8. 成功回调
          results[i] = value; // 按照顺序存储结果
          completedCount++;

          // 9. 当所有 Promise 都成功 fulfilled 时,resolve 整个 Promise
          if (completedCount === promiseArray.length) {
            resolve(results);
          }
        },
        (reason) => {
          // 10. 失败回调:只要有一个 Promise 被 rejected,就立即 reject 整个 Promise
          reject(reason);
        }
      );
    }
  });
}

代码解释:

  1. 参数校验: 检查传入的参数 promises 是否是可迭代对象。如果不是,则 reject 一个 TypeError
  2. 转换为数组: 将传入的 promises 转换为数组,方便后续处理。
  3. 空数组处理: 如果传入的是空数组,则直接 resolve 一个空数组。这是 Promise.all 的一个特殊行为。
  4. 结果数组: results 数组用于存储每个 Promise 的 fulfillment 值。
  5. 计数器: completedCount 变量用于记录已完成的 Promise 的数量。
  6. 循环遍历: 遍历 Promise 数组,对每个 Promise 进行处理。
  7. Promise.resolve包装: 使用 Promise.resolve(promise) 将数组中的每一项都包装成 Promise 对象。这样可以处理数组中包含非 Promise 值的情况。
  8. 成功回调: 当一个 Promise 成功 fulfilled 时,将它的 fulfillment 值存储到 results 数组中,并将 completedCount 加 1。
  9. 全部完成:completedCount 等于 Promise 数组的长度时,表示所有 Promise 都已成功 fulfilled,此时 resolve 整个 Promise,并将 results 数组作为 fulfillment 值传递给 resolve 函数。
  10. 失败回调: 只要有一个 Promise 被 rejected,就立即 reject 整个 Promise,并将第一个被 rejected 的 Promise 的 reason 传递给 reject 函数。

测试一下:

const promise1 = Promise.resolve(1);
const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 100));
const promise3 = Promise.resolve(3);

myPromiseAll([promise1, promise2, promise3])
  .then((values) => {
    console.log('Success:', values); // 输出: Success: [ 1, 2, 3 ]
  })
  .catch((error) => {
    console.error('Error:', error);
  });

const promise4 = Promise.reject('Error 4');
const promise5 = Promise.resolve(5);

myPromiseAll([promise4, promise5])
  .then((values) => {
    console.log('Success:', values);
  })
  .catch((error) => {
    console.error('Error:', error); // 输出: Error: Error 4
  });

第二幕:Promise.race – 速度与激情

Promise.race 就像一场赛跑比赛,它会等待第一个 Promise 完成(无论是 fulfilled 还是 rejected),然后立即返回该 Promise 的结果。 就像百米赛跑,谁先冲过终点线,就以谁的结果为准。

Promise.race 的规则:

  • 输入: 接收一个 Promise 数组(或者任何 iterable 对象,只要里面的元素都是 Promise-like 对象)。
  • 输出: 返回一个新的 Promise。
  • 结果: 一旦数组中的某个 Promise fulfilled 或 rejected,返回的 Promise 也会立即 fulfilled 或 rejected,并且它的 value 或 reason 就是第一个 fulfilled 或 rejected 的 Promise 的 value 或 reason。

Promise.race 的使用场景:

  • 超时控制: 设置一个超时 Promise,如果请求在指定时间内没有返回,就 reject 该 Promise。
  • 竞争资源: 多个 Promise 竞争同一个资源,哪个 Promise 先拿到资源,就使用哪个 Promise 的结果。

手写实现 Promise.race

function myPromiseRace(promises) {
  return new Promise((resolve, reject) => {
    // 1. 参数校验:确保传入的是一个可迭代对象
    if (!promises || typeof promises[Symbol.iterator] !== 'function') {
      return reject(new TypeError('Argument is not iterable'));
    }
    // 2. 将可迭代对象转换为数组
    const promiseArray = Array.from(promises);

    // 3. 如果传入的是空数组,则返回一个永远pending的Promise
    if (promiseArray.length === 0) {
        return; // 返回一个永远pending的Promise
    }

    // 4. 遍历 Promise 数组
    for (let i = 0; i < promiseArray.length; i++) {
      const promise = promiseArray[i];

      // 5. 确保数组中的每一项都是一个 Promise 对象
      Promise.resolve(promise).then(
        (value) => {
          // 6. 只要有一个 Promise 成功 fulfilled,就立即 resolve 整个 Promise
          resolve(value);
        },
        (reason) => {
          // 7. 只要有一个 Promise 被 rejected,就立即 reject 整个 Promise
          reject(reason);
        }
      );
    }
  });
}

代码解释:

  1. 参数校验: 检查传入的参数 promises 是否是可迭代对象。如果不是,则 reject 一个 TypeError
  2. 转换为数组: 将传入的 promises 转换为数组,方便后续处理。
  3. 空数组处理: 如果传入的是空数组,则返回一个永远 pending 的 Promise,符合规范.
  4. 循环遍历: 遍历 Promise 数组,对每个 Promise 进行处理。
  5. Promise.resolve包装: 使用 Promise.resolve(promise) 将数组中的每一项都包装成 Promise 对象。
  6. 成功回调: 只要有一个 Promise 成功 fulfilled,就立即 resolve 整个 Promise,并将它的 fulfillment 值传递给 resolve 函数。
  7. 失败回调: 只要有一个 Promise 被 rejected,就立即 reject 整个 Promise,并将它的 reason 传递给 reject 函数。

测试一下:

const promise6 = new Promise((resolve) => setTimeout(() => resolve(6), 200));
const promise7 = new Promise((resolve) => setTimeout(() => resolve(7), 100));

myPromiseRace([promise6, promise7])
  .then((value) => {
    console.log('Success:', value); // 输出: Success: 7
  })
  .catch((error) => {
    console.error('Error:', error);
  });

const promise8 = new Promise((resolve, reject) => setTimeout(() => reject('Error 8'), 50));
const promise9 = new Promise((resolve) => setTimeout(() => resolve(9), 100));

myPromiseRace([promise8, promise9])
  .then((value) => {
    console.log('Success:', value);
  })
  .catch((error) => {
    console.error('Error:', error); // 输出: Error: Error 8
  });

myPromiseRace([])
.then((value) => {
    console.log('Success:', value);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

总结:Promise.all vs. Promise.race

特性 Promise.all Promise.race
目标 等待所有 Promise 完成 等待第一个 Promise 完成
成功条件 所有 Promise 都 fulfilled 任何一个 Promise fulfilled
失败条件 任何一个 Promise 被 rejected 任何一个 Promise 被 rejected
返回值(成功) 包含所有 fulfillment 值的数组,顺序与输入一致 第一个 fulfilled 的 Promise 的 value
返回值(失败) 第一个 rejected 的 Promise 的 reason 第一个 rejected 的 Promise 的 reason

彩蛋:Promise.allSettled

ES2020 引入了一个新的 Promise 方法:Promise.allSettled。它会等待所有的 Promise 都 settled(fulfilled 或 rejected),然后返回一个包含每个 Promise 结果的数组,无论它们是成功还是失败。 这就像一个更宽容的 Promise.all,它不会因为任何一个 Promise 的失败而导致整个操作失败。

结尾:Promise 的世界,精彩无限

Promise.allPromise.race 是 Promise 中非常重要的两个方法,掌握它们可以让你更有效地处理异步操作,编写更健壮的代码。 希望今天的讲解能够帮助你更好地理解和使用这两个强大的工具。 记住,编程的乐趣在于不断学习和探索,Promise 的世界还有很多精彩等待你去发现! 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注