我的个人博客

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 解决就可以开始下一个 promise 时,这种方法很有用。

但它真的是并行运行(或一次性全部运行)吗?

答案是否定的。JavaScript 是单线程编程语言,所以它不能同时运行多个任务(除了某些情况,如 web workers)。 Promise.all() 实际上是并发运行它们,而不是并行!

有什么区别?

并发编程 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()])
// 或
await Promise.all(
  items.map(async (item) => {
    await doSomething(item)
  })
)

或者顺序运行:

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

参考资料

祝你 promise 愉快!