[英]JavaScript: Promise.All() and Async / Await and Map()
[英]What is the difference between async/await forEach and Promise.all + map
在類似問題的已接受答案中,答案表明forEach
調用只是拋出一個承諾然后退出。 我認為應該是這種情況,因為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");
});
});
})();
它並行工作 這是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
不要將 async/await 與 forEach 一起使用。 要么使用for-of
循環,要么使用Promise.all()
和array.map()
。
如果您對 Promise 和 async/await 有一個大致的了解,那么關於 Promise.all( promise.all()
+ array.map()
和.forEach()
之間差異的 TL;DR 是不可能等待forEach()
本身。 是的,您可以在.forEach()
中並行運行任務,就像使用.map()
一樣,但是您不能等待所有這些並行任務完成,然后在它們全部完成后執行某些操作。 使用.map()
而不是.forEach()
的全部意義在於,您可以獲得一個 Promise 列表,使用Promise.all()
收集它們,然后等待整個過程。 要明白我的意思,只需在forEach(async () => ...)
之后放置一個console.log('Finished')
,你會看到"finished"
在一切都完成運行之前被注銷.forEach()
循環。 我的建議是不要將.forEach()
與異步邏輯一起使用(實際上,這些天沒有理由再使用.forEach()
了,正如我在下面進一步解釋的那樣)。
對於那些需要更深入的東西的人,這個答案的其余部分將更詳細地介紹,首先對 Promise 進行一個小的回顧,然后深入解釋這些方法的行為方式有何不同以及為什么.forEach()
是當涉及到異步/等待時,總是劣質的解決方案。
出於本次討論的目的,您只需要記住,promise 是一個特殊的對象,它承諾某項任務將在未來的某個時間完成。 您可以通過.then()
將偵聽器附加到承諾,以便在任務完成時收到通知,並接收已解決的值。
async
函數只是一個無論如何都會返回承諾的函數。 即使您執行async function doThing() { return 2 }
,它也不會返回 2,它會返回一個立即解析為值 2 的 Promise。請注意,異步函數總是會立即返回一個 Promise,即使它該功能需要很長時間才能運行。 這就是為什么它被稱為“承諾”的原因,它承諾函數最終會完成運行,如果你想在函數完成時收到通知,你可以通過.then()
或await
向它添加一個事件監聽器.
await
是一種特殊語法,可讓您暫停執行異步函數,直到 promise 解決。 await
只會影響它直接在里面的功能。 在幕后, 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.
}
...
}
如果你能很好地掌握這些原則,那么你應該能夠理解接下來的部分。
for-of
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()
await
導致main()
暫停指定的延遲,之后循環繼續, console.log()
執行,循環再次開始下一次迭代,開始一個新的延遲。
這個應該希望有點直截了當。
Promise.all()
+ array.map()
一起使用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()
如果你還記得我們關於 Promise 和 async/await 的快速入門,你會記得await
只影響它直接在里面的函數,導致該函數暫停。 在這種情況下,來自await
await wait(delay)
的 await 不會導致main()
像在前面的示例中那樣暫停,而是會導致傳遞給delays.map()
的回調暫停,因為那是它直接在里面的功能。
因此,我們有delays.map()
,它將調用提供的回調,為delays
數組中的每個delay
調用一次。 回調是異步的,所以它總是會立即返回一個 Promise。 回調將使用不同的延遲參數開始執行,但不可避免地會遇到await wait(delay)
行,暫停回調的執行。
因為.map()
的回調返回了一個delays.map()
將返回一個 Promise 數組, Promise.all()
將接收這些 Promise,並將它們組合成一個超級 Promise,當所有數組中的承諾解決了。 現在,我們await
promise.all()
返回的超級承諾。 這個await
在main()
內部,導致main()
暫停,直到所有提供的 Promise 都解決。 因此,我們在main()
內部創建了一堆獨立的異步任務,讓它們都按自己的時間完成,然后暫停main()
本身的執行,直到所有這些任務都完成。
首先,你真的不需要使用forEach()
來做任何事情。 它在for-of
循環之前就出現了,而且for-of
在各方面都比forEach()
好。 for-of
可以針對任何可迭代對象運行,您可以使用break
並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()
一樣。 這是因為forEach()
內部的await
只影響回調,而不是main()
。 因此,當我們運行delays.forEach()
時,我們為delays
中的每個delay
調用這個異步函數,啟動一堆異步任務。 問題是沒有什么會等待異步任務完成,事實上,等待它們是不可能的。 異步回調在每次調用時都會返回 Promise,但與.map()
不同的是, .forEach()
完全忽略了回調的返回值。 .forEach()
收到承諾,然后簡單地忽略它。 這使得我們無法像以前那樣通過Promise.all()
將 Promise 組合在一起並await
它們。 因此,您會注意到"finish"
會立即注銷,因為我們不會讓main()
等待這些承諾完成。 這可能不是您想要的。
(又名原始答案)
它之所以有效,是因為 for 循環仍在運行,並且您仍然啟動了一堆異步任務。 問題是你沒有在等待他們。 當然你在.forEach()
中使用 await ,但這只會導致.forEach()
回調等待,它不會暫停你的外部函數。 如果您在異步 IIFE 的末尾放置一個console.log()
,您可以看到這一點,您的 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!') })();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.