簡體   English   中英

async/await forEach 和 Promise.all + 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

對於只看到問題標題的 Google 員工

不要將 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 和 async/await 入門

出於本次討論的目的,您只需要記住,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()返回的超級承諾。 這個awaitmain()內部,導致main()暫停,直到所有提供的 Promise 都解決。 因此,我們在main()內部創建了一堆獨立的異步任務,讓它們都按自己的時間完成,然后暫停main()本身的執行,直到所有這些任務都完成。

.forEach() 的問題

首先,你真的不需要使用forEach()來做任何事情。 它在for-of循​​環之前就出現了,而且for-of在各方面都比forEach()好。 for-of可以針對任何可迭代對象運行,您可以使用breakcontinue使用它們,最重要的是, 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM