实现 Promise.all

手写实现 Promise.all 方法,接收一个 Promise 数组并返回一个新的 Promise,当所有 Promise 都成功时返回结果数组,任一失败则立即返回失败原因。

问题

Promise.all 是 JavaScript 中用于处理多个异步操作的重要方法。它接收一个可迭代对象(通常是 Promise 数组),返回一个新的 Promise。当所有输入的 Promise 都成功时,返回的 Promise 才会成功,结果是所有 Promise 结果组成的数组;只要有一个 Promise 失败,返回的 Promise 就会立即失败,返回第一个失败的原因。

解答

/**
 * 实现 Promise.all
 * @param {Iterable} promises - 可迭代对象,通常是 Promise 数组
 * @returns {Promise} 返回一个新的 Promise
 */
function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    // 将可迭代对象转换为数组
    const promiseArray = Array.from(promises);
    
    // 处理空数组的情况
    if (promiseArray.length === 0) {
      resolve([]);
      return;
    }
    
    // 用于存储结果的数组
    const results = [];
    // 记录已完成的 Promise 数量
    let completedCount = 0;
    
    // 遍历所有 Promise
    promiseArray.forEach((promise, index) => {
      // 使用 Promise.resolve 包装,确保非 Promise 值也能正常处理
      Promise.resolve(promise)
        .then(value => {
          // 将结果存储到对应的索引位置,保证顺序
          results[index] = value;
          completedCount++;
          
          // 当所有 Promise 都完成时,resolve 结果数组
          if (completedCount === promiseArray.length) {
            resolve(results);
          }
        })
        .catch(error => {
          // 任何一个 Promise 失败,立即 reject
          reject(error);
        });
    });
  });
}

使用示例

// 示例 1: 所有 Promise 都成功
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = new Promise((resolve) => {
  setTimeout(() => resolve(3), 1000);
});

promiseAll([p1, p2, p3])
  .then(results => {
    console.log(results); // 输出: [1, 2, 3]
  })
  .catch(error => {
    console.error(error);
  });

// 示例 2: 有 Promise 失败
const p4 = Promise.resolve(4);
const p5 = Promise.reject('Error occurred');
const p6 = Promise.resolve(6);

promiseAll([p4, p5, p6])
  .then(results => {
    console.log(results);
  })
  .catch(error => {
    console.error(error); // 输出: Error occurred
  });

// 示例 3: 包含非 Promise 值
promiseAll([1, 2, Promise.resolve(3)])
  .then(results => {
    console.log(results); // 输出: [1, 2, 3]
  });

// 示例 4: 空数组
promiseAll([])
  .then(results => {
    console.log(results); // 输出: []
  });

关键点

  • 使用 Array.from 转换可迭代对象:确保输入可以是任何可迭代对象,不仅限于数组

  • 空数组边界处理:当输入为空数组时,应立即 resolve 一个空数组

  • 使用 Promise.resolve 包装:确保数组中的非 Promise 值也能被正确处理,统一转换为 Promise

  • 保持结果顺序:使用索引 results[index] 存储结果,而不是 push,确保结果数组的顺序与输入数组一致

  • 计数器机制:使用 completedCount 记录已完成的 Promise 数量,当等于总数时才 resolve

  • 快速失败机制:任何一个 Promise 失败时立即 reject,不等待其他 Promise 完成

  • 避免重复 resolve/reject:一旦某个 Promise 失败并 reject 后,其他 Promise 的结果将被忽略