简体   繁体   English

async/await forEach 和 Promise.all + map 有什么区别

[英]What is the difference between async/await forEach and Promise.all + map

In the accepted answer to a similar question the answer states that a forEach call just throw a promise then exit.类似问题的已接受答案中,答案表明forEach调用只是抛出一个承诺然后退出。 I think this should be the case as forEach returns undefined but why does the following code work?我认为应该是这种情况,因为forEach返回undefined但为什么下面的代码有效?

const networkFunction = (callback) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(callback());
    }, 200);
  });
};

(async () => {
  const numbers = [0, 1, 2];
  // works in parallel
  numbers.forEach(async (num) => {
    await networkFunction(() => {
      console.log("For Each Function: Hello");
    });
  });
})();

And it works in parallel this is the output of time node main.js # main.js contains only the mentioned code它并行工作 这是time node main.js # main.js contains only the mentioned code

❯ time node main.js
For Each Function: Hello
For Each Function: Hello
For Each Function: Hello

________________________________________________________
Executed in  365.63 millis    fish           external
   usr time  126.02 millis  964.00 micros  125.05 millis
   sys time   36.68 millis  618.00 micros   36.06 millis

For Googlers who only saw the question title对于只看到问题标题的 Google 员工

Don't use async/await with forEach.不要将 async/await 与 forEach 一起使用。 Either use a for-of loop, or use Promise.all() with array.map() .要么使用for-of循​​环,要么使用Promise.all()array.map()

If you have a general understanding on promises and async/await, the TL;DR on the differences between promise.all() + array.map() and .forEach() , is that it's impossible to await a forEach() itself.如果您对 Promise 和 async/await 有一个大致的了解,那么关于 Promise.all( promise.all() + array.map().forEach()之间差异的 TL;DR 是不可能等待forEach()本身。 Yes, you can run tasks in parallel in .forEach() just like you can with .map() , but you can't wait for all of those parallel tasks to finish, then do something once they've all finished.是的,您可以在.forEach()中并行运行任务,就像使用.map()一样,但是您不能等待所有这些并行任务完成,然后在它们全部完成后执行某些操作。 The whole point of using .map() instead of .forEach() is so you can get a list of promises, collect them with Promise.all() , then await the whole thing.使用.map()而不是.forEach()的全部意义在于,您可以获得一个 Promise 列表,使用Promise.all()收集它们,然后等待整个过程。 To see what I mean, just put a console.log('Finished') after a forEach(async () => ...) , and you'll see the "finished" get logged out before everything has finished running in the .forEach() loop.要明白我的意思,只需在forEach(async () => ...)之后放置一个console.log('Finished') ,你会看到"finished"在一切都完成运行之前被注销.forEach()循环。 My advice would be to just not use .forEach() with async logic (and really there's not a reason use ever use .forEach() anymore these days, as I explain further down).我的建议是不要将.forEach()与异步逻辑一起使用(实际上,这些天没有理由再使用.forEach()了,正如我在下面进一步解释的那样)。

For those who need something a bit more in depth, the rest of this answer will dive into more detail, first giving a small review on promises, then showing an in-depth explanation on how these approaches behave differently and why .forEach() is always the inferior solution when it comes to async/await.对于那些需要更深入的东西的人,这个答案的其余部分将更详细地介绍,首先对 Promise 进行一个小的回顾,然后深入解释这些方法的行为方式有何不同以及为什么.forEach()是当涉及到异步/等待时,总是劣质的解决方案。

A primer on promises and async/await Promise 和 async/await 入门

For the purposes of this discussion, you just have to remember that a promise is a special object that's promising that some task is going to be completed at some point in the future.出于本次讨论的目的,您只需要记住,promise 是一个特殊的对象,它承诺某项任务将在未来的某个时间完成。 You can attach listeners to a promise via .then() , to get notified when the task is completed, and to receive the resolved value.您可以通过.then()将侦听器附加到承诺,以便在任务完成时收到通知,并接收已解决的值。

An async function is simply a function that will always return a promise, no matter what. async函数只是一个无论如何都会返回承诺的函数。 Even if you do async function doThing() { return 2 } , it's not going to return 2, it's going to return a promise that immediately resolves to the value 2. Note that an asynchronous function will always return a promise immediately, even if it takes a long time for the function to run.即使您执行async function doThing() { return 2 } ,它也不会返回 2,它会返回一个立即解析为值 2 的 Promise。请注意,异步函数总是会立即返回一个 Promise,即使它该功能需要很长时间才能运行。 This is why it's called a "promise", it's promising that the function will eventually finish running, and if you want to be notified for when the function has finished, you can add an event listener to it, via .then() or await .这就是为什么它被称为“承诺”的原因,它承诺函数最终会完成运行,如果你想在函数完成时收到通知,你可以通过.then()await向它添加一个事件监听器.

await is special syntax that lets you pause execution of an async function until a promise resolves. await是一种特殊语法,可让您暂停执行异步函数,直到 promise 解决。 await will only effect the function it's directly inside. await只会影响它直接在里面的功能。 Behind the scenes, await is simply adding a special event listener to the promise's .then() , so it can know when the promise resolves and what value it resolves with.在幕后, await只是简单地将一个特殊的事件监听器添加到 promise 的.then()中,因此它可以知道 promise 何时解析以及它解析的值是什么。

async fn1() {
  async fn2() {
    await myPromise // This pauses execution of fn2(), not fn1()!
  }
  ...
}

async function fn1() {
  function fn2() {
    await myPromise // An error, because fn2() is not async.
  }
  ...
}

If you can get a good grasp of these principles, then you should be able to understand the next sections.如果你能很好地掌握这些原则,那么你应该能够理解接下来的部分。

for-of

A for-of loop lets you execute your asynchronous tasks in serial, one after another. for-of循​​环让您一个接一个地串行执行异步任务。 For example:例如:

 const delays = [1000, 1400, 1200]; // A function that will return a // promise that resolves after the specified // amount of time. const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) async function main() { console.log('start') for (const delay of delays) { await wait(delay) console.log('Finished waiting for the delay ' + delay) } console.log('finish') } main()

The await causes main() to pause for the specified delay, after which the loop continues, the console.log() executes, and the loop starts again with the next iteration, beginning a new delay. await导致main()暂停指定的延迟,之后循环继续, console.log()执行,循环再次开始下一次迭代,开始一个新的延迟。

This one should hopefully be a little straightforwards.这个应该希望有点直截了当。

Promise.all() + array.map() Promise.all() + array.map()

The use of Promise.all() and array.map() together lets you effectively run many asynchronous tasks in parallel, for example, we can wait for many different delays to finish at the same time.一起使用Promise.all()array.map()可以让您有效地并行运行许多异步任务,例如,我们可以等待许多不同的延迟同时完成。

 const delays = [1000, 1400, 1200]; // A function that will return a // promise that resolves after the specified // amount of time. const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) async function main() { console.log('start') await Promise.all(delays.map(async delay => { await wait(delay) console.log('Finished waiting for the delay ' + delay) })) console.log('finish') } main()

If you remember back to our quick primer on promises and async/await, you'll remember that await only effects the function it's directly inside, causing that function to pause.如果你还记得我们关于 Promise 和 async/await 的快速入门,你会记得await只影响它直接在里面的函数,导致该函数暂停。 In this case, the await from await wait(delay) will not be causing main() to pause like it did in the previous example, instead, it's going to cause the callback being passed into delays.map() to pause, because that's the function it's directly inside.在这种情况下,来自await await wait(delay)的 await 不会导致main()像在前面的示例中那样暂停,而是会导致传递给delays.map()的回调暂停,因为那是它直接在里面的功能。

So, we have delays.map() which will call the supplied callback, once for each delay inside of the delays array.因此,我们有delays.map() ,它将调用提供的回调,为delays数组中的每个delay调用一次。 The callback is asynchronous, so it's always going to return a promise immediately .回调是异步的,所以它总是会立即返回一个 Promise。 The callback will begin executing with different delay arguments, but will inevitably hit the await wait(delay) line, pausing execution of the callback.回调将使用不同的延迟参数开始执行,但不可避免地会遇到await wait(delay)行,暂停回调的执行。

Because the callback for .map() returns a promise, delays.map() is going to return an array of promises, which Promise.all() will then receive, and will combine them together into one super promise that resolves when all of the promises in the array resolves.因为.map()的回调返回了一个delays.map()将返回一个 Promise 数组, Promise.all()将接收这些 Promise,并将它们组合成一个超级 Promise,当所有数组中的承诺解决了。 Now, we await the super promise returned by promise.all() .现在,我们await promise.all()返回的超级承诺。 This await is inside main() , causing main() to pause until all of the provided promises resolve.这个awaitmain()内部,导致main()暂停,直到所有提供的 Promise 都解决。 Thus, we've created a bunch of independent asynchronous tasks inside of main() , let them all finish on their own time, and then paused the execution of main() itself until all of these tasks have finished.因此,我们在main()内部创建了一堆独立的异步任务,让它们都按自己的时间完成,然后暂停main()本身的执行,直到所有这些任务都完成。

The problem with .forEach() .forEach() 的问题

First of all, you really don't need to use forEach() for anything.首先,你真的不需要使用forEach()来做任何事情。 It came out before the for-of loop did, and for-of is simply better than forEach() in every way.它在for-of循​​环之前就出现了,而且for-of在各方面都比forEach()好。 for-of can run against any iterable, you can use break and continue with them, and, most importantly, await will work as expected in for-of but not in forEach() . for-of可以针对任何可迭代对象运行,您可以使用breakcontinue使用它们,最重要的是, await将在for-of中按预期工作,但在forEach()中不会。 Here's why:原因如下:

 const delays = [1000, 1400, 1200]; // A function that will return a // promise that resolves after the specified // amount of time. const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) async function main() { console.log('start') delays.forEach(async delay => { await wait(delay) console.log('Finished waiting for the delay ' + delay) }) console.log('finish') } main()

First, you'll notice that .forEach() will cause the tasks to run in parallel instead of serial, just like with .map() .首先,您会注意到.forEach()将导致任务并行而不是串行运行,就像使用.map()一样。 This is because the await inside of forEach() is only effecting the callback, not main() .这是因为forEach()内部的await只影响回调,而不是main() So, when we run delays.forEach() , we call this asynchronous function for each delay in delays , kicking off a bunch of asynchronous tasks.因此,当我们运行delays.forEach()时,我们为delays中的每个delay调用这个异步函数,启动一堆异步任务。 The problem is that nothing will wait for the asynchronous tasks to finish, indeed, it's impossible to wait for them.问题是没有什么会等待异步任务完成,事实上,等待它们是不可能的。 The asynchronous callback is returning promises each time it gets called, but unlike .map() , .forEach() completely ignores the return value of it's callback.异步回调在每次调用时都会返回 Promise,但与.map()不同的是, .forEach()完全忽略了回调的返回值。 .forEach() receives the promise, and then simply ignores it. .forEach()收到承诺,然后简单地忽略它。 This makes it impossible to group the promises together and await them all via Promise.all() like we did previously.这使得我们无法像以前那样通过Promise.all()将 Promise 组合在一起并await它们。 Because of this, you'll notice that "finish" gets logged out immediately, since we're never causing main() to wait for these promises to finish.因此,您会注意到"finish"会立即注销,因为我们不会让main()等待这些承诺完成。 This is likely not what you want.这可能不是您想要的。


A specific answer to the original question对原始问题的具体回答

(aka the original answer) (又名原始答案)

It works because the for loop still runs, and you still start a bunch of async tasks.它之所以有效,是因为 for 循环仍在运行,并且您仍然启动了一堆异步任务。 The problem is that you're not awaiting them.问题是你没有在等待他们。 Sure you use await within .forEach() , but that only causes the .forEach() callback to wait, it doesn't pause your outer function.当然你在.forEach()中使用 await ,但这只会导致.forEach()回调等待,它不会暂停你的外部函数。 You can see this if you put a console.log() at the end of your async IIFE, your console.log() will fire immediately before all of your requests have finished.如果您在异步 IIFE 的末尾放置一个console.log() ,您可以看到这一点,您的 console.log() 将在所有请求完成之前立即触发。 If you had used Promise.all() instead, then that console.log() would fire after the requests have finished.如果您使用 Promise.all() 代替,那么 console.log() 将在请求完成后触发。

Broken version:破解版:

 const networkFunction = (callback) => { return new Promise((resolve) => { setTimeout(() => { resolve(callback()); }, 200); }); }; (async () => { const numbers = [0, 1, 2]; // works in parallel numbers.forEach(async (num) => { await networkFunction(() => { console.log("For Each Function: Hello"); }); }); console.log('All requests finished!') })();

Fixed version:固定版本:

 const networkFunction = (callback) => { return new Promise((resolve) => { setTimeout(() => { resolve(callback()); }, 200); }); }; (async () => { const numbers = [0, 1, 2]; // works in parallel await Promise.all(numbers.map(async (num) => { await networkFunction(() => { console.log("For Each Function: Hello"); }); })); console.log('All requests finished!') })();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM