关于 javascript:map() 与 async 与 promise.all()

map() with async vs promise.all()

如果我有一个元素数组并且我想对它们进行并行操作。
我会使用 promise.all()

我知道 promise.all() 接受一系列Promise。如果我错了,请纠正我,我不这么认为。
在这里,它清楚地表明。

The Promise.all() method returns a single Promise that fulfills when all of the promises passed as an iterable have been fulfilled or when the iterable contains no promises or when the iterable contains promises that have been fulfilled and non-promises that have been returned. It rejects with the reason of the first promise that rejects, or with the error caught by the first argument if that argument has caught an error inside it using try/catch/throw blocks.

所以,是的,我们可以将简单的函数传递给 promise.all(),如果它们返回则解析,如果抛出错误则拒绝。
现在看看下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const promises = todayAssignedJobs.map(async todayAssigned => {
  const [leaderboard, created] = await Leaderboard.findOrCreate({...});

  if (!created) {
    const rating = (todayAssigned.rating + leaderboard.rating * leaderboard.jobs_completed) / (leaderboard.jobs_completed + 1);
    const commission = todayAssigned.commission + leaderboard.commission;
    const jobsCompleted = leaderboard.jobs_completed + 1;

    await Leaderboard.update({
      rating,
      commission,
      jobs_completed: jobsCompleted,
      updated_by: 'system',
    }, {
      where: {
        id: leaderboard.id,
      },
    });
  }

  await AssignedJob.update({
    is_leaderboard_generated: true,
  }, {
    where: {
      id: todayAssigned.id,
    },
  });
});

await Promise.all(promises);

在这里,我有一个疑问。
我们正在迭代数组的每个元素并对它们进行异步操作。他们没有明确返回任何东西。
所以,我认为 map 也在这里做并行操作。
为什么要在这里使用 promise.all() 呢?


.map() 不是Promise感知的。所以,当你像你一样向它传递一个 async 回调时,它不会注意返回的Promise。因此,它只是一个接一个地运行循环,而不是等待任何返回的Promise。因此,在 .map() 循环中启动的所有异步操作将同时进行。

如果那是你想要的,并且你想收集所有返回的 Promise,以便以后可以看到它们何时都用 Promise.all() 完成,那么这个模式很好用:

1
Promise.all(someArray.map(callbackThatReturnsAPromiseHere))

而且,这是一种常见的设计模式。事实上,Bluebird Promise 库有一个特殊的函数将这两者结合起来,称为 Promise.map()。它还提供了另一个不错的功能,让您可以控制一次可以运行多少并发异步操作(因为它的 map() 操作是 promise-aware)。

听起来您正试图弄清楚是否应该只使用 .map() 而不使用 Promise.all()。如果这样做,您将并行运行异步操作,但您将不知道它们何时全部完成,也不知道有能力收集所有结果。您将在返回的Promise数组上使用 Promise.all() 以了解它们何时全部完成和/或收集它们的已解决结果。

仅供参考,.map() 只是一个普通的循环。它没有任何特殊的异步功能或任何特殊的并行运行功能。如果你愿意,你可以用 for 循环做同样的事情。它不会暂停您的 async 回调以等待它完成,因此运行它的副作用是您启动了一堆并行异步操作。


如果你想在所有操作完成后做某事,你只需要Promise.all。例如:

1
2
3
4
5
6
7
8
const promises = todayAssignedJobs.map(async todayAssigned => {
  // lots of async stuff
});

await Promise.all(promises);

// now, all Promises have resolved
// alert the user that the leaderboard is completely updated

如果您在确定所有 Promise 都已完成后不需要发生任何事情,那么 Promise.all 就没有意义了 - 您也可以在循环中创建 Promise 并让他们保持原样。在这种情况下,由于您不会使用 Promise 的结果数组,因此使用 forEach (这是用于副作用的数组方法)会更合适。

不过有一个问题 - 你没有处理错误,但应该处理错误,否则它们会给出警告或退出 Node 进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const processJob = async (todayAssigned) => {
  const [leaderboard, created] = await Leaderboard.findOrCreate({...});

  if (!created) {
    // ...

// ...
todayAssignedJobs.forEach(async todayAssigned => {
  try {
    await processJob(todayAssigned);
  } catch(e) {
    // handle errors
  }
});

这里 Promise.all 的目的是能够在继续之前等待所有的Promise。

如果你在 await Promise.all(promises); 之前添加了一个console.log,它会在任何promise 解决之前同步运行,而console.log 紧随该行之后只会在所有promise 都解决之后才会出现。