Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save anonimusprogramus/120c651d387a585d56d8617076cc9882 to your computer and use it in GitHub Desktop.

Select an option

Save anonimusprogramus/120c651d387a585d56d8617076cc9882 to your computer and use it in GitHub Desktop.

Revisions

  1. @joeytwiddle joeytwiddle revised this gist Aug 23, 2022. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions async-await-forEach-alternatives.md
    Original 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 been awarded (but after they have all started being awarded).
    - 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 them all to complete before proceeding to `sendEmailToAdmin()`.
    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.
    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. (And importantly in the reduce case, each invocation of the callback function waits for the previous promise to resolve, to ensure sequential processing.)
    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()`:

  2. @joeytwiddle joeytwiddle revised this gist Sep 30, 2018. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions async-await-forEach-alternatives.md
    Original 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.
  3. @joeytwiddle joeytwiddle revised this gist Sep 30, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion async-await-forEach-alternatives.md
    Original 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: `map` and `reduce` and `reduceRight` if used correctly
    _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.

  4. @joeytwiddle joeytwiddle revised this gist Sep 30, 2018. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion async-await-forEach-alternatives.md
    Original 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();

    players.forEach(async (player) => {
    // BAD
    await players.forEach(async (player) => {
    await givePrizeToPlayer(player);
    });

  5. @joeytwiddle joeytwiddle revised this gist Sep 30, 2018. 1 changed file with 25 additions and 1 deletion.
    26 changes: 25 additions & 1 deletion async-await-forEach-alternatives.md
    Original 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.
    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.
  6. @joeytwiddle joeytwiddle revised this gist Sep 30, 2018. 1 changed file with 6 additions and 2 deletions.
    8 changes: 6 additions & 2 deletions async-await-forEach-alternatives.md
    Original 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 as more functional, but personally I find the `for-of` loop is clearer.
    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 more or less the same behaviour as the `for...of` above.
    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.
  7. @joeytwiddle joeytwiddle revised this gist Sep 14, 2018. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions async-await-forEach-alternatives.md
    Original 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 been awarded (but after they have all started being awarded).
    - 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.
    _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 to them all to complete before proceeding to `sendEmailToAdmin()`.
    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`

  8. @joeytwiddle joeytwiddle revised this gist Sep 14, 2018. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions async-await-forEach-alternatives.md
    Original 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
  9. @joeytwiddle joeytwiddle revised this gist Jun 9, 2018. 1 changed file with 13 additions and 7 deletions.
    20 changes: 13 additions & 7 deletions async-await-forEach-alternatives.md
    Original 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. 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)!
    - 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 to do it?
    ## So how should we write this?

    ### Process each player in serial

    @@ -37,7 +37,9 @@ for (const player of players) {
    }
    ```

    (You could also use `for(...;...;...)` but that is more verbose than we need.)
    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.
  10. @joeytwiddle joeytwiddle revised this gist Jun 9, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions async-await-forEach-alternatives.md
    Original 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.***
    _TLDR:_ Use `for...of` instead of `forEach` in asynchronous code.

    ### Why not?
    ### 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.)

  11. @joeytwiddle joeytwiddle revised this gist Jun 9, 2018. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions async-await-forEach-alternatives.md
    Original 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.*
    ***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.)
    ### 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:

  12. @joeytwiddle joeytwiddle revised this gist Jun 9, 2018. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions async-await-forEach-alternatives.md
    Original 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:
  13. @joeytwiddle joeytwiddle revised this gist Jun 9, 2018. 1 changed file with 5 additions and 3 deletions.
    8 changes: 5 additions & 3 deletions async-await-forEach-alternatives.md
    Original 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.)

    The following forEach loop might not do what it looks like:
    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.
    - 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 were awarded (they could have all thrown an error)!
    - 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?

  14. @joeytwiddle joeytwiddle revised this gist Jun 9, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion async-await-forEach-alternatives.md
    Original 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.
    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) => {
  15. @joeytwiddle joeytwiddle revised this gist Jun 9, 2018. 1 changed file with 7 additions and 3 deletions.
    10 changes: 7 additions & 3 deletions async-await-forEach-alternatives.md
    Original 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());
    ```

    Some people recommend this approach as more "functional" but I find the `for-of` loop is clearer.
    ```
  16. @joeytwiddle joeytwiddle revised this gist Jun 9, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions async-await-forEach-alternatives.md
    Original 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 given.
    - As a result, sendEmailToAdmin sends the email before the prizes have been awarded.
    - 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?

  17. @joeytwiddle joeytwiddle renamed this gist Jun 9, 2018. 1 changed file with 0 additions and 0 deletions.
  18. @joeytwiddle joeytwiddle created this gist Jun 9, 2018.
    49 changes: 49 additions & 0 deletions async-await-forEach-alternatives.js
    Original 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.