JavaScript Promise.all() 是并行还是顺序执行的?

Long straight road with trees on the side
Published on
/4 mins read/---

假设你有一个异步任务列表(每个任务都返回一个 Promise)。

promises.js
let promise1 = async function () {
  /* ... */
}
let promise2 = async function () {
  /* ... */
}
let promise3 = async function () {
  /* ... */
}

你会选择如何运行它们?

一个接一个地等待每个 promise:

await promise1()
await promise2()
await promise3()
// 执行其他操作

或者一次性运行所有的 promise:

await Promise.all([promise1(), promise2(), promise3()])
// 执行其他操作

第一种方法是顺序执行,一个接一个地运行。这意味着下一个 promise 只有在前一个 promise 完成后才会开始执行。

就像这样:

promise-hell.js
promise1().then(() => {
  promise2().then(() => {
    promise3().then(() => {
     // 执行其他操作
    })
  })
})

第二种方法常被称为**"并行"**执行,意味着所有的 promise 会同时开始执行。 当你不需要等待前一个 promise 完成就可以开始执行下一个时,这种方法就很有用。

但它真的是并行(或同时)执行的吗?

答案是否定的。JavaScript 是单线程编程语言,所以它不能同时执行多个任务(除了一些特殊情况,比如使用 web workers)。 Promise.all() 实际上是并发执行这些 promise,而不是并行执行!

那么,这两者有什么区别呢?

并发编程 vs 并行编程

简单来说:并发编程是同时处理多件事情,而并行编程是同时执行多件事情。

你也可以参考 Haskell wiki 上的这个精彩解释。

给 9 岁小朋友的简单例子:

  • 并发:2 队顾客在一个收银员那里轮流点餐(两队交替点餐)。
  • 并行:2 队顾客在 2 个收银员那里同时点餐。

因此,Promise.all() 的工作原理是,它将所有 promise 添加到事件循环队列中并一起调用它们。 但它会等待每个 promise 解决后才继续。 如果第一个 promise 被拒绝,Promise.all 就会停止,除非你自己处理错误(比如用 .catch())。

这就是并发和并行的主要区别。在并发执行中,promise 一个接一个运行,但不必等待前面的完成。它们同时取得进展。 相比之下,并行执行在独立的进程中同时运行 promise。 这使得它们能够完全独立地按照各自的速度推进。

总结

标题问题的答案是:Promise.all() 是并发执行的,所有的 promise 几乎同时执行,但不是并行执行。

如果你有一组互不依赖的 promise,你可以选择并发(或类并行)执行它们:

concurrently.js
await Promise.all([promise1(), promise2(), promise3()])
// or
await Promise.all(
  items.map(async (item) => {
    await doSomething(item)
  })
)

或者顺序执行:

sequentially.js
for (let item of items) {
  await doSomething(item)
}

参考资料

Happy promise-ing!