O.P. 的问题是要求澄清另一个 StackOverflow 问题在这里找到 https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop/37576787#37576787。如需进一步阅读以及有关此一般主题的许多其他精彩答案,请查看链接。
对于只看到问题标题的 Google 员工
不要将 async/await 与 forEach 一起使用。要么使用for-of
循环,或使用Promise.all()
with array.map()
.
如果您对 Promise 和 async/await 有一般性的了解,那么 TL;DR 会介绍两者之间的差异promise.all()
+ array.map()
and .forEach()
,是不可能等待forEach()
本身。是的,您可以并行运行任务.forEach()
就像你可以的那样.map()
,但您不能等待所有这些并行任务完成,然后在它们全部完成后执行某些操作。使用的全部要点.map()
代替.forEach()
这样你就可以得到一份承诺清单,用以下方式收集它们Promise.all()
,然后等待整个事情发生。要明白我的意思,只需添加一个console.log('Finished')
之后forEach(async () => ...)
,你会看到"finished"
在一切完成运行之前注销.forEach()
环形。我的建议是不要使用.forEach()
使用异步逻辑(实际上没有理由使用.forEach()
这些天不再这样了,正如我在下面进一步解释的那样)。
对于那些需要更深入了解的人,本答案的其余部分将深入探讨更多细节,首先对 Promise 进行简短回顾,然后深入解释这些方法的行为方式有何不同以及原因.forEach()
当涉及到 async/await 时,它始终是较差的解决方案。
Promise 和 async/await 入门
出于本次讨论的目的,您只需记住承诺是一个特殊的对象,它承诺某些任务将在未来的某个时刻完成。您可以通过以下方式将侦听器附加到 Promise:.then()
,在任务完成时收到通知,并接收解析值。
An async
function 只是一个无论如何都会返回 Promise 的函数。即使你这样做async function doThing() { return 2 }
,它不会返回 2,而是返回一个立即解析为值 2 的 Promise。请注意,异步函数将始终立即返回 Promise,即使该函数需要很长时间才能运行。这就是为什么它被称为“承诺”,它承诺该函数最终将完成运行,如果您想在函数完成时收到通知,您可以向其添加一个事件侦听器,通过.then()
or await
.
await
是一种特殊的语法,可让您暂停异步函数的执行,直到承诺解决。await
只会影响其直接内部的功能。在幕后,await
只是简单地向 Promise 添加一个特殊的事件监听器.then()
,因此它可以知道 Promise 何时解析以及解析的值是什么。
async function fn1() {
async function fn2() {
await myPromise // This pauses execution of fn2(), not fn1()!
}
...
}
async function fn1() {
function fn2() {
await myPromise // An error, because fn2() is not async.
}
...
}
如果您能够很好地掌握这些原则,那么您应该能够理解接下来的部分。
for-of
A for-of
循环允许您一个接一个地串行执行异步任务。例如:
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()
暂停指定的延迟,之后循环继续,console.log()
执行,循环再次开始下一次迭代,开始新的延迟。
希望这个应该有点简单。
Promise.all()
+ array.map()
指某东西的用途Promise.all()
and array.map()
Together 可以让您有效地并行运行许多异步任务,例如,我们可以等待许多不同的延迟同时完成。
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()
如果您还记得我们关于 Promise 和 async/await 的快速入门知识,您就会记得await
只影响其直接内部的函数,导致该函数暂停。在这种情况下,await
from await wait(delay)
不会造成main()
像前面的示例一样暂停,相反,它将导致回调被传递到delays.map()
暂停,因为这是它直接位于内部的函数。
所以,我们有delays.map()
这将调用提供的回调,每个delay
里面的delays
大批。回调是异步的,因此它总是会返回一个承诺立即地。回调将开始使用不同的延迟参数执行,但不可避免地会遇到await wait(delay)
行,暂停回调的执行。
因为回调为.map()
返回一个承诺,delays.map()
将返回一系列承诺,其中Promise.all()
然后将接收它们,并将它们组合在一起形成一个超级承诺,当数组中的所有承诺都解决时,该超级承诺就会解决。现在我们await
返回的超级承诺promise.all()
. This await
在里面main()
,导致main()
暂停,直到所有提供的承诺都得到解决。因此,我们在其中创建了一堆独立的异步任务main()
,让他们都在自己的时间完成,然后暂停执行main()
直到所有这些任务完成。
.forEach() 的问题
首先,你真的不需要使用forEach()
为了任何东西。之前就出来了for-of
循环做了,并且for-of
简直比forEach()
各方面。for-of
可以针对任何可迭代运行,您可以使用break
and continue
和他们在一起,最重要的是对于这个话题,await
将按预期工作for-of
但不在forEach()
。原因如下:
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()
首先,你会注意到.forEach()
将导致任务并行运行而不是串行运行,就像.map()
。这是因为await
代替forEach()
只影响回调,不影响main()
。所以,当我们跑步时delays.forEach()
,我们为每个调用这个异步函数delay
in delays
,启动一堆异步任务。问题是,没有什么会等待异步任务完成,实际上,不可能等待它们。异步回调每次被调用时都会返回 Promise,但与.map()
, .forEach()
完全忽略其回调的返回值。.forEach()
收到承诺,然后简单地忽略它。这使得不可能将承诺组合在一起并且await
他们都通过Promise.all()
就像我们之前做的那样。正因为如此,你会注意到"finish"
立即注销,因为我们从来没有造成main()
等待这些承诺完成。这可能不是您想要的。
原问题的具体答案
(又名原始答案)
它之所以有效,是因为 for 循环仍然运行,并且您仍然启动一堆异步任务。问题是你没有在等他们。当然你在里面使用了await.forEach()
,但这只会导致.forEach()
回调等待,它不会暂停你的外部函数。如果你把一个console.log()
在 async IIFE 结束时,您的 console.log() 将在所有请求完成之前立即触发。如果您使用 Promise.all() 代替,那么 console.log() 将在请求完成后触发。
破损版本:
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!')
})();
固定版本:
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!')
})();