`Promise.all`与`Promise.race`的并发控制:实现一个自定义的`Promise.allSettled`函数。

并发控制:Promise.all, Promise.race 与自定义 Promise.allSettled

大家好,今天我们来深入探讨 JavaScript 中 Promise 的并发控制,重点关注 Promise.allPromise.race,以及如何实现一个自定义的 Promise.allSettled 函数。 理解这些概念对于编写高效、健壮的异步代码至关重要。

Promise.all:等待所有 Promise 完成

Promise.all 接收一个 Promise 数组(或者任何可迭代的 Promise 对象),并返回一个新的 Promise。 这个新 Promise 的行为取决于输入 Promise 的状态:

  • 所有 Promise 都成功 fulfilled: 返回的 Promise 会以一个包含所有 Promise 的 fulfillment 值的数组来 fulfill。 数组元素的顺序与输入 Promise 的顺序一致。
  • 任何一个 Promise rejected: 返回的 Promise 立即以第一个被 reject 的 Promise 的 reason 来 reject。

可以用一张表格来更清晰地展示:

输入 Promise 状态 返回 Promise 状态 返回值 / 原因
所有 fulfill fulfill fulfillment 值数组
至少一个 reject reject 第一个 reject 的 reason

示例:

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

Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values); // 输出: [1, 2, 3] (约 100ms 后)
  })
  .catch((error) => {
    console.error(error); // 不会被调用
  });

const promise4 = Promise.resolve(4);
const promise5 = Promise.reject('出错了');
const promise6 = Promise.resolve(6);

Promise.all([promise4, promise5, promise6])
  .then((values) => {
    console.log(values); // 不会被调用
  })
  .catch((error) => {
    console.error(error); // 输出: 出错了
  });

实现原理:

Promise.all 的内部实现可以大致如下:

function myPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Argument must be an array'));
    }

    const results = [];
    let completedCount = 0;

    if (promises.length === 0) {
      return resolve(results); // 空数组的情况
    }

    for (let i = 0; i < promises.length; i++) {
      const promise = promises[i];

      // 处理非 Promise 值,将其包装成 Promise
      Promise.resolve(promise)
        .then((value) => {
          results[i] = value;
          completedCount++;

          if (completedCount === promises.length) {
            resolve(results);
          }
        })
        .catch((reason) => {
          reject(reason); // 只要有一个 reject,就立即 reject
        });
    }
  });
}

// 使用示例:
myPromiseAll([promise1, promise2, promise3])
  .then((values) => console.log("myPromiseAll success:", values))
  .catch((error) => console.error("myPromiseAll error:", error));

myPromiseAll([promise4, promise5, promise6])
  .then((values) => console.log("myPromiseAll success:", values))
  .catch((error) => console.error("myPromiseAll error:", error));

关键点:

  • 数组顺序: Promise.all 保证结果数组的顺序与输入 Promise 的顺序一致。
  • 短路行为: 只要有一个 Promise reject,整个 Promise.all 立即 reject。
  • 非 Promise 值: Promise.all 会将非 Promise 值包装成 Promise。 Promise.resolve 实现了这个功能。
  • 空数组: 如果传入一个空数组,Promise.all 会立即 resolve,并返回一个空数组。

应用场景:

  • 并行请求: 当需要同时发起多个网络请求,并且需要等待所有请求都完成后才能进行下一步操作时,可以使用 Promise.all
  • 数据聚合: 从多个数据源获取数据,并将它们合并成一个最终结果。
  • UI 更新: 等待多个动画效果完成后再更新 UI。

Promise.race:竞速 Promise

Promise.race 同样接收一个 Promise 数组(或者任何可迭代的 Promise 对象),并返回一个新的 Promise。 但与 Promise.all 不同,Promise.race 的行为取决于第一个完成的 Promise:

  • 第一个 Promise fulfill: 返回的 Promise 会以第一个 fulfill 的 Promise 的值来 fulfill。
  • 第一个 Promise reject: 返回的 Promise 会以第一个 reject 的 Promise 的 reason 来 reject。

同样使用表格总结:

输入 Promise 状态 返回 Promise 状态 返回值 / 原因
第一个 fulfill fulfill 第一个 fulfill 的值
第一个 reject reject 第一个 reject 的 reason

示例:

const promise7 = new Promise((resolve) => setTimeout(() => resolve(7), 50));
const promise8 = new Promise((resolve) => setTimeout(() => resolve(8), 100));

Promise.race([promise7, promise8])
  .then((value) => {
    console.log(value); // 输出: 7 (约 50ms 后)
  })
  .catch((error) => {
    console.error(error); // 不会被调用
  });

const promise9 = new Promise((resolve, reject) => setTimeout(() => reject('超时'), 20));
const promise10 = new Promise((resolve) => setTimeout(() => resolve(10), 100));

Promise.race([promise9, promise10])
  .then((value) => {
    console.log(value); // 不会被调用
  })
  .catch((error) => {
    console.error(error); // 输出: 超时 (约 20ms 后)
  });

实现原理:

function myPromiseRace(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Argument must be an array'));
    }

    for (let i = 0; i < promises.length; i++) {
      const promise = promises[i];

      Promise.resolve(promise)
        .then(resolve) // 只要有一个 fulfill,就立即 resolve
        .catch(reject); // 只要有一个 reject,就立即 reject
    }
  });
}

// 使用示例:
myPromiseRace([promise7, promise8])
  .then((value) => console.log("myPromiseRace success:", value))
  .catch((error) => console.error("myPromiseRace error:", error));

myPromiseRace([promise9, promise10])
  .then((value) => console.log("myPromiseRace success:", value))
  .catch((error) => console.error("myPromiseRace error:", error));

关键点:

  • 竞速: Promise.race 只关心第一个完成的 Promise。
  • 短路行为: 只要有一个 Promise fulfill 或 reject,整个 Promise.race 立即 fulfill 或 reject。
  • 非 Promise 值: Promise.race 会将非 Promise 值包装成 Promise。
  • 空数组: 如果传入一个空数组,Promise.race 返回的 Promise 将永远 pending,因为它永远不会有 resolve 或者 reject 的机会。

应用场景:

  • 超时控制: 可以用 Promise.race 来实现 Promise 的超时机制。 如果在指定时间内 Promise 没有完成,就 reject 该 Promise。
  • 备选方案: 当有多个备选方案时,可以使用 Promise.race 来选择最先完成的方案。
  • 取消操作: 可以用一个 reject 的 Promise 来取消一个正在进行的 Promise。

Promise.allSettled:兼顾成功与失败

Promise.allSettled 接收一个 Promise 数组(或者任何可迭代的 Promise 对象),并返回一个新的 Promise。 与 Promise.all 不同,Promise.allSettled 会等待所有 Promise 都完成(无论 fulfill 还是 reject)后才 resolve。 返回的 Promise 会以一个包含每个 Promise 结果的数组来 fulfill。

数组中的每个元素都是一个对象,包含以下属性:

  • status:字符串,表示 Promise 的状态,可以是 "fulfilled""rejected"
  • value:如果 Promise fulfill,则包含 fulfillment 值。
  • reason:如果 Promise reject,则包含 rejection reason。

用表格描述:

输入 Promise 状态 返回 Promise 状态 返回值
所有 fulfill 或 reject fulfill 包含每个 Promise 结果的数组 (status, value/reason)

示例:

const promise11 = Promise.resolve(11);
const promise12 = Promise.reject('出错了');
const promise13 = new Promise((resolve) => setTimeout(() => resolve(13), 50));

Promise.allSettled([promise11, promise12, promise13])
  .then((results) => {
    console.log(results);
    // 输出:
    // [
    //   { status: 'fulfilled', value: 11 },
    //   { status: 'rejected', reason: '出错了' },
    //   { status: 'fulfilled', value: 13 }
    // ]
  });

实现原理:

function myPromiseAllSettled(promises) {
  return new Promise((resolve) => {
    if (!Array.isArray(promises)) {
      return resolve(new TypeError('Argument must be an array')); // allSettled 应当 resolve 而不是 reject
    }

    const results = [];
    let completedCount = 0;

    if (promises.length === 0) {
      return resolve(results); // 空数组的情况
    }

    for (let i = 0; i < promises.length; i++) {
      const promise = promises[i];

      Promise.resolve(promise)
        .then((value) => {
          results[i] = { status: 'fulfilled', value: value };
          completedCount++;

          if (completedCount === promises.length) {
            resolve(results);
          }
        })
        .catch((reason) => {
          results[i] = { status: 'rejected', reason: reason };
          completedCount++;

          if (completedCount === promises.length) {
            resolve(results);
          }
        });
    }
  });
}

// 使用示例:
myPromiseAllSettled([promise11, promise12, promise13])
  .then((results) => console.log("myPromiseAllSettled:", results));

关键点:

  • 不短路: Promise.allSettled 会等待所有 Promise 都完成,无论 fulfill 还是 reject。
  • 结果格式: 返回的结果数组中的每个元素都包含 statusvaluereason 属性。
  • 非 Promise 值: Promise.allSettled 会将非 Promise 值包装成 Promise。
  • 空数组: 如果传入一个空数组,Promise.allSettled 会立即 resolve,并返回一个空数组。

应用场景:

  • 错误处理: 当需要执行多个操作,并且希望知道哪些操作成功,哪些操作失败时,可以使用 Promise.allSettled。 即使某些操作失败,也需要继续执行其他操作。
  • 审计日志: 记录所有操作的结果,包括成功和失败的操作。
  • 资源清理: 无论操作成功与否,都需要执行一些清理工作。

总结:并发控制,各有侧重

Promise.all, Promise.race, 和 Promise.allSettled 提供了不同的并发控制策略。 Promise.all 等待所有 Promise 成功,Promise.race 关注最先完成的 Promise,而 Promise.allSettled 兼顾所有 Promise 的结果,提供了更全面的控制和错误处理能力。

深入理解,灵活应用

理解 Promise.allPromise.racePromise.allSettled 的原理和应用场景,可以帮助我们编写更高效、更健壮的异步代码。 根据不同的需求选择合适的并发控制策略,可以提高代码的性能和可维护性。

灵活组合,解决问题

这些并发控制方法可以灵活组合使用,解决更复杂的问题。 例如,可以使用 Promise.race 实现超时机制,并结合 Promise.allSettled 来处理超时后的其他 Promise 的结果。

发表回复

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