Forked from joeytwiddle/async-await-forEach-alternatives.md
Created
April 4, 2023 20:02
-
-
Save anonimusprogramus/120c651d387a585d56d8617076cc9882 to your computer and use it in GitHub Desktop.
Revisions
-
joeytwiddle revised this gist
Aug 23, 2022 . 1 changed file with 4 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -23,7 +23,7 @@ What's wrong with it? - The promises returned by the iterator function are not handled. So if one of them throws an error, the error won't be caught. (In Node 10, if no `unhandledrejection` listener has been registered, that will cause the process to crash!) - Because `forEach` does not wait for each promise to resolve, all the prizes are awarded in parallel, not serial (one by one). - So the loop actually finishes iterating before any of the prizes have finished being awarded! (But after they have all started being awarded). - As a result, `sendEmailToAdmin()` sends the email before any of the prizes have finished being awarded. Maybe none of them will end up being awarded (they might all throw an error)! ## So how should we write this? @@ -54,7 +54,7 @@ await Promise.all(players.map(async (player) => { })); ``` This will start awarding all the prizes at once, but it will wait for all of them to complete before proceeding to `sendEmailToAdmin()`. ### Process each player in serial, using `Array.prototype.reduce` @@ -81,9 +81,9 @@ _TLDR:_ Only `map()`, `reduce()`, `flatMap()` and `reduceRight()` if used correc async-await works naturally with `for` loops and `while` loops, because they are written in the original function body. But when you call out to another function, it can only work with async-await if it returns a promise, and if that promise is handled (awaited or `.then()`-ed). That is why we can use `.reduce()` and `.map()` above, because in both cases we return a promise (or an array of promises) which we can await. (In the reduce case, each invocation of the callback function waits for the previous promise to resolve, to ensure sequential processing.) But most array functions will not give us a promise back, or allow a promise to be passed from one call to the next, so they cannot be used asynchronously. So, for example, you can not use asynchronous code inside `array.some()` or `array.filter()`: -
joeytwiddle revised this gist
Sep 30, 2018 . 1 changed file with 4 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -62,11 +62,15 @@ Some people recommend this approach: ```js await players.reduce(async (a, player) => { // Wait for the previous item to finish processing await a; // Process this item await givePrizeToPlayer(player); }, Promise.resolve()); ``` (We are using the accumulator `a` not as a total or a summary, but just as a way to pass the promise from the previous item's callback to the next item's callback, so that we can wait for the previous item to finish being processed.) This has pretty much the same behaviour as the `for...of` above, but is slightly harder to read. However it is recommended by the Airbnb style guide because it can reduce the browser bundle size. `for...of` requires iterators, and some browsers require a polyfill for iterators, and that polyfill is quite large. You can decide on the trade-off between bundle size and developer convenience. -
joeytwiddle revised this gist
Sep 30, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -73,7 +73,7 @@ However it is recommended by the Airbnb style guide because it can reduce the br ## So which array functions can I use? _TLDR:_ Only `map()`, `reduce()`, `flatMap()` and `reduceRight()` if used correctly async-await works naturally with `for` loops and `while` loops, because they are written in the original function body. -
joeytwiddle revised this gist
Sep 30, 2018 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -11,7 +11,8 @@ For example, the following forEach loop might not do what it appears to do: ```js const players = await this.getWinners(); // BAD await players.forEach(async (player) => { await givePrizeToPlayer(player); }); -
joeytwiddle revised this gist
Sep 30, 2018 . 1 changed file with 25 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -68,4 +68,28 @@ await players.reduce(async (a, player) => { This has pretty much the same behaviour as the `for...of` above, but is slightly harder to read. However it is recommended by the Airbnb style guide because it can reduce the browser bundle size. `for...of` requires iterators, and some browsers require a polyfill for iterators, and that polyfill is quite large. You can decide on the trade-off between bundle size and developer convenience. ## So which array functions can I use? TLDR: `map` and `reduce` and `reduceRight` if used correctly async-await works naturally with `for` loops and `while` loops, because they are written in the original function body. But when you call out to another function, it can only work with async-await if it returns a promise. That is why we can use `.reduce()` and `.map()` above, because in both cases we return a promise (or an array of promises) which we can await. (And importantly in the reduce case, each invocation of the callback function waits for the previous promise to resolve, to ensure sequential processing.) But most array functions will not give us a promise back, or allow a promise to be passed from one call to the next, so they cannot be used asynchronously. So, for example, you can not use asynchronous code inside `array.some()` or `array.filter()`: ```js // BAD const playersWithGoodScores = await players.filter(async (player) => { const score = await calculateLatestScore(player); return score >= 100; }); ``` It might look like that should work but it won't, because `filter` was never designed with promises in mind. When `filter` calls your callback function, it will get a `Promise` back, but instead of awaiting that promise, it will just see the promise as "truthy", and immediately accept the player, regardless of what their score will eventually be. You may be able to find a library of array functions that can work asynchronously, but the standard array functions do not. -
joeytwiddle revised this gist
Sep 30, 2018 . 1 changed file with 6 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -45,6 +45,8 @@ _Note:_ The airbnb style guide recommends _not_ using `for...of` for _web apps_ ### Process all the players in parallel If the order doesn't matter, it may be quicker to process all the players in parallel. ```js await Promise.all(players.map(async (player) => { await givePrizeToPlayer(player); @@ -55,7 +57,7 @@ This will start awarding all the prizes at once, but it will wait for them all t ### Process each player in serial, using `Array.prototype.reduce` Some people recommend this approach: ```js await players.reduce(async (a, player) => { @@ -64,4 +66,6 @@ await players.reduce(async (a, player) => { }, Promise.resolve()); ``` This has pretty much the same behaviour as the `for...of` above, but is slightly harder to read. However it is recommended by the Airbnb style guide because it can reduce the browser bundle size. `for...of` requires iterators, and some browsers require a polyfill for iterators, and that polyfill is quite large. You can decide on the trade-off between bundle size and developer convenience. -
joeytwiddle revised this gist
Sep 14, 2018 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -22,7 +22,7 @@ What's wrong with it? - The promises returned by the iterator function are not handled. So if one of them throws an error, the error won't be caught. (In Node 10, if no `unhandledrejection` listener has been registered, that will cause the process to crash!) - Because `forEach` does not wait for each promise to resolve, all the prizes are awarded in parallel, not serial (one by one). - So the loop actually finishes iterating before any of the prizes have finished been awarded (but after they have all started being awarded). - As a result, `sendEmailToAdmin()` sends the email before any of the prizes have finished being awarded. Maybe none of them will end up being awarded (they might all throw an error)! ## So how should we write this? @@ -41,7 +41,7 @@ This loop will wait for one prize to be awarded before proceeding to the next on You could also use a traditional `for(...;...;...)` here but that is more verbose than we need. _Note:_ The airbnb style guide recommends _not_ using `for...of` for _web apps_ at the current time (2018), because it requires a large polyfill. If you are working in the browser, use the traditional for mentioned above, or `Array.reduce()` described below. ### Process all the players in parallel @@ -51,7 +51,7 @@ await Promise.all(players.map(async (player) => { })); ``` This will start awarding all the prizes at once, but it will wait for them all to complete before proceeding to `sendEmailToAdmin()`. ### Process each player in serial, using `Array.prototype.reduce` -
joeytwiddle revised this gist
Sep 14, 2018 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -41,6 +41,8 @@ This loop will wait for one prize to be awarded before proceeding to the next on You could also use a traditional `for(...;...;...)` here but that is more verbose than we need. Note: The airbnb style guide recommends _not_ using `for...of` for web apps at the current time (2018), because it requires a large polyfill. If you are working in the browser, use the traditional for mentioned above, or `Array.reduce()` described below. ### Process all the players in parallel ```js -
joeytwiddle revised this gist
Jun 9, 2018 . 1 changed file with 13 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -20,12 +20,12 @@ await sendEmailToAdmin('All prizes awarded'); What's wrong with it? - The promises returned by the iterator function are not handled. So if one of them throws an error, the error won't be caught. (In Node 10, if no `unhandledrejection` listener has been registered, that will cause the process to crash!) - Because `forEach` does not wait for each promise to resolve, all the prizes are awarded in parallel, not serial (one by one). - So the loop actually finishes iterating before any of the prizes have been awarded (but after they have all started being awarded). - As a result, `sendEmailToAdmin()` sends the email before any of the prizes have finished being awarded. Maybe none of them will end up being awarded (they might all throw an error)! ## So how should we write this? ### Process each player in serial @@ -37,7 +37,9 @@ for (const player of players) { } ``` This loop will wait for one prize to be awarded before proceeding to the next one. You could also use a traditional `for(...;...;...)` here but that is more verbose than we need. ### Process all the players in parallel @@ -47,6 +49,8 @@ await Promise.all(players.map(async (player) => { })); ``` This will start awarding all the prizes at once, but it will wait to them all to complete before proceeding to `sendEmailToAdmin()`. ### Process each player in serial, using `Array.prototype.reduce` Some people recommend this approach as more functional, but personally I find the `for-of` loop is clearer. @@ -56,4 +60,6 @@ await players.reduce(async (a, player) => { await a; await givePrizeToPlayer(player); }, Promise.resolve()); ``` This has more or less the same behaviour as the `for...of` above. -
joeytwiddle revised this gist
Jun 9, 2018 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,8 +1,8 @@ ## Do not use forEach with async-await _TLDR:_ Use `for...of` instead of `forEach` in asynchronous code. ### The problem `Array.prototype.forEach` is not designed for asynchronous code. (It was not suitable for promises, and it is not suitable for async-await.) -
joeytwiddle revised this gist
Jun 9, 2018 . 1 changed file with 4 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,8 +1,10 @@ ## Do not use forEach with async-await ***TLDR: Use `for...of` instead of `forEach` in asynchronous code.*** ### Why not? `Array.prototype.forEach` is not designed for asynchronous code. (It was not suitable for promises, and it is not suitable for async-await.) For example, the following forEach loop might not do what it appears to do: -
joeytwiddle revised this gist
Jun 9, 2018 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,7 @@ ## Do not use forEach with async-await > *TLDR: Use `for...of` instead of `forEach` in asynchronous code.* `Array.prototype.forEach` can not be used for asynchronous code. (It could not be used with promises, and it can not be used with async-await.) For example, the following forEach loop might not do what it appears to do: -
joeytwiddle revised this gist
Jun 9, 2018 . 1 changed file with 5 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,8 @@ ## Do not use forEach with async-await `Array.prototype.forEach` can not be used for asynchronous code. (It could not be used with promises, and it can not be used with async-await.) For example, the following forEach loop might not do what it appears to do: ```js const players = await this.getWinners(); @@ -14,10 +16,10 @@ await sendEmailToAdmin('All prizes awarded'); What's wrong with it? - The promises returned by the iterator function are not handled. If one of them throws an error, the error won't be caught. (In Node 10, the process might crash!) - The prizes are awarded in parallel, not serial (one by one). - The loop finishes iterating before any of the prizes have been awarded. - As a result, sendEmailToAdmin sends the email before the prizes have been awarded. Maybe none of them will end up being awarded (they might all throw an error)! ## So how to do it? -
joeytwiddle revised this gist
Jun 9, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -43,7 +43,7 @@ await Promise.all(players.map(async (player) => { ### Process each player in serial, using `Array.prototype.reduce` Some people recommend this approach as more functional, but personally I find the `for-of` loop is clearer. ```js await players.reduce(async (a, player) => { -
joeytwiddle revised this gist
Jun 9, 2018 . 1 changed file with 7 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -23,12 +23,16 @@ What's wrong with it? ### Process each player in serial Fortunately if your language has async-await then it will also have the `for...of` construction, so you can use that. ```js for (const player of players) { await givePrizeToPlayer(player); } ``` (You could also use `for(...;...;...)` but that is more verbose than we need.) ### Process all the players in parallel ```js @@ -39,11 +43,11 @@ await Promise.all(players.map(async (player) => { ### Process each player in serial, using `Array.prototype.reduce` Some people recommend this approach as more "functional" but personally I find the `for-of` loop is clearer. ```js await players.reduce(async (a, player) => { await a; await givePrizeToPlayer(player); }, Promise.resolve()); ``` -
joeytwiddle revised this gist
Jun 9, 2018 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -16,8 +16,8 @@ What's wrong with it? - The promises returned by the iterator function are not handled. If one of them throws an error, the error won't be caught. - The prizes are awarded in parallel, not serial (one by one). - The loop finishes iterating before any of the prizes have been awarded. - As a result, sendEmailToAdmin sends the email before the prizes have been awarded. Maybe none of them were awarded (they could have all thrown an error)! ## So how to do it? -
joeytwiddle renamed this gist
Jun 9, 2018 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
joeytwiddle created this gist
Jun 9, 2018 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,49 @@ `Array.prototype.forEach` can not be used for asynchronous code. (It could not be used with promises, and it can not be used with async-await.) The following forEach loop might not do what it looks like: ```js const players = await this.getWinners(); players.forEach(async (player) => { await givePrizeToPlayer(player); }); await sendEmailToAdmin('All prizes awarded'); ``` What's wrong with it? - The promises returned by the iterator function are not handled. If one of them throws an error, the error won't be caught. - The prizes are awarded in parallel, not serial (one by one). - The loop finishes iterating before any of the prizes have been given. - As a result, sendEmailToAdmin sends the email before the prizes have been awarded. ## So how to do it? ### Process each player in serial ```js for (const player of players) { await givePrizeToPlayer(player); } ``` ### Process all the players in parallel ```js await Promise.all(players.map(async (player) => { await givePrizeToPlayer(player); })); ``` ### Process each player in serial, using `Array.prototype.reduce` ```js await players.reduce(async (a, player) => { await a; await givePrizeToPlayer(player); }, Promise.resolve()); ``` Some people recommend this approach as more "functional" but I find the `for-of` loop is clearer.