【前端面试知识点】手写一个符合Promise/A+规范的Promise.all,需处理错误和空迭代对象的情况

0 阅读3分钟

Promise.all 是 ES6 中 Promise 的一个静态方法,它接收一个可迭代对象(通常是数组)作为参数,并返回一个新的 Promise 实例。其行为符合 Promise/A+ 规范对 Promise 组合的要求,具体规则如下:


功能描述

  • 输入:一个可迭代对象(如数组),其元素可以是任意值(包括普通值、thenable 对象或 Promise 实例)。
  • 输出:一个新的 Promise 实例,该实例的状态和结果由输入的所有元素共同决定。

执行逻辑

  1. 统一包装
    对于输入中的每个元素,都会通过 Promise.resolve(element) 将其转换为一个 Promise 实例(非 Promise 值会被包装为已兑现的 Promise)。

  2. 等待所有完成

    • 如果所有转换后的 Promise 都变为已兑现(fulfilled) ,则返回的 Promise 变为已兑现,其结果是一个数组,包含所有兑现值,且顺序与输入顺序完全一致
    • 如果任何一个转换后的 Promise 变为已拒绝(rejected) ,则返回的 Promise 会立即变为已拒绝,拒绝原因为第一个被拒绝的 Promise 的拒绝理由(忽略后续其他 Promise 的结果)。
  3. 空迭代对象处理
    如果传入的可迭代对象为空(如空数组),则返回的 Promise 立即以已兑现状态完成,结果为空数组 []


错误处理

  • 短路机制:只要有一个 Promise 失败,Promise.all 返回的 Promise 就会立即失败,不会等待其他未完成的 Promise(但那些未完成的 Promise 仍然会继续执行,只是其结果被忽略)。
  • 异常传递:任何被拒绝的 Promise 的拒绝原因会直接作为返回 Promise 的拒绝理由。

符合 Promise/A+ 规范 Promise.all 手写实现,包含错误处理和空迭代对象的处理:

javascript

Promise.myAll = function(iterable) {
  // 将可迭代对象转换为数组,若不可迭代则抛出 TypeError(与原生行为一致)
  const items = Array.from(iterable);
  
  // 处理空迭代对象:立即成功,返回空数组
  if (items.length === 0) {
    return Promise.resolve([]);
  }

  return new Promise((resolve, reject) => {
    const results = new Array(items.length); // 按索引存储结果
    let remaining = items.length;             // 剩余未完成的 Promise 数量

    items.forEach((item, index) => {
      // 统一包装非 Promise 值,确保 .then 可用
      Promise.resolve(item).then(
        value => {
          results[index] = value;              // 保持原顺序
          remaining--;
          if (remaining === 0) {
            resolve(results);                  // 全部成功,返回结果数组
          }
        },
        reason => {
          reject(reason);                       // 只要一个失败,立即拒绝
          // 注意:一旦 reject,Promise 状态已定,后续的 resolve/reject 将被忽略
        }
      );
    });
  });
};

关键特性说明

  • 空迭代对象:直接返回 Promise.resolve([]),符合规范。
  • 错误处理:任一 Promise 失败立即调用 reject,后续完成不影响已定的拒绝状态。
  • 保持顺序:通过预分配数组和按索引赋值,确保结果顺序与输入一致。
  • 非 Promise 值:使用 Promise.resolve 包装,使得普通值也能作为成功结果。
  • 边界情况Array.from(iterable) 会在参数不可迭代时抛出 TypeError,与原生 Promise.all 行为一致。

测试示例

javascript

// 空数组
Promise.myAll([]).then(console.log); // []

// 混合值
Promise.myAll([1, Promise.resolve(2), 3]).then(console.log); // [1, 2, 3]

// 错误情况
Promise.myAll([Promise.reject('error'), Promise.resolve(1)])
  .catch(err => console.error(err)); // 'error'

// 异步 Promise
const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
const p3 = 3;
Promise.myAll([p1, p2, p3]).then(console.log); // 约100ms后输出 [1, 2, 3]