标准答案

  1. 入参通常是一个可迭代对象,里面可以是 Promise,也可以是普通值。
  2. 返回值必须是一个新的 Promise。
  3. 所有任务应该并发开始,而不是一个完成后再启动下一个。
  4. 结果数组要按入参顺序保存,不能按完成顺序保存。
  5. 任意一个任务 reject,外层 Promise 就 reject;如果入参为空,应立即 resolve 一个空数组。

题目解析

Promise.all 容易写错的地方在“并发”和“顺序”同时存在。任务完成顺序是不确定的,但返回数组必须保持输入顺序,所以每个任务都要记住自己的 index。

它不是错误收集工具。只要有一个任务失败,外层就进入 rejected;如果业务要知道每个任务成功还是失败,应该考虑 allSettled 或自己封装结果结构。

手写时还要把普通值包成 Promise.resolve(value),这样入参里混合 Promise 和普通值也能得到一致行为。

代码示例

下面是一个覆盖顺序、空数组和快速失败的简化实现。

JavaScript
function promiseAll(items) {
  return new Promise((resolve, reject) => {
    const list = Array.from(items)
    const results = []
    let fulfilledCount = 0

    if (list.length === 0) {
      resolve([])
      return
    }

    list.forEach((item, index) => {
      Promise.resolve(item).then(
        value => {
          results[index] = value
          fulfilledCount += 1

          if (fulfilledCount === list.length) {
            resolve(results)
          }
        },
        reject
      )
    })
  })
}

常见误区

  • 用 push 保存结果,导致返回顺序变成完成顺序。
  • 忘记处理空数组,外层 Promise 永远不 resolve。
  • 把任务写成串行 await,失去 Promise.all 的并发意义。

作者信息