Using async/await with a forEach loop

1,276,094

Solution 1

Sure the code does work, but I'm pretty sure it doesn't do what you expect it to do. It just fires off multiple asynchronous calls, but the printFiles function does immediately return after that.

Reading in sequence

If you want to read the files in sequence, you cannot use forEach indeed. Just use a modern for … of loop instead, in which await will work as expected:

async function printFiles () {
  const files = await getFilePaths();

  for (const file of files) {
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  }
}

Reading in parallel

If you want to read the files in parallel, you cannot use forEach indeed. Each of the async callback function calls does return a promise, but you're throwing them away instead of awaiting them. Just use map instead, and you can await the array of promises that you'll get with Promise.all:

async function printFiles () {
  const files = await getFilePaths();

  await Promise.all(files.map(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  }));
}

Solution 2

With ES2018, you are able to greatly simplify all of the above answers to:

async function printFiles () {
  const files = await getFilePaths()

  for await (const contents of files.map(file => fs.readFile(file, 'utf8'))) {
    console.log(contents)
  }
}

See spec: proposal-async-iteration


2018-09-10: This answer has been getting a lot of attention recently, please see Axel Rauschmayer's blog post for further information about asynchronous iteration.

Solution 3

Instead of Promise.all in conjunction with Array.prototype.map (which does not guarantee the order in which the Promises are resolved), I use Array.prototype.reduce, starting with a resolved Promise:

async function printFiles () {
  const files = await getFilePaths();

  await files.reduce(async (promise, file) => {
    // This line will wait for the last async function to finish.
    // The first iteration uses an already resolved Promise
    // so, it will immediately continue.
    await promise;
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  }, Promise.resolve());
}

Solution 4

The p-iteration module on npm implements the Array iteration methods so they can be used in a very straightforward way with async/await.

An example with your case:

const { forEach } = require('p-iteration');
const fs = require('fs-promise');

(async function printFiles () {
  const files = await getFilePaths();

  await forEach(files, async (file) => {
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  });
})();

Solution 5

Here are some forEachAsync prototypes. Note you'll need to await them:

Array.prototype.forEachAsync = async function (fn) {
    for (let t of this) { await fn(t) }
}

Array.prototype.forEachAsyncParallel = async function (fn) {
    await Promise.all(this.map(fn));
}

Note while you may include this in your own code, you should not include this in libraries you distribute to others (to avoid polluting their globals).

Share:
1,276,094
Saad
Author by

Saad

I love open source, teaching, and giving back to the community. Check out my GitHub if you want to see some of my work.

Updated on July 08, 2022

Comments

  • Saad
    Saad almost 2 years

    Are there any issues with using async/await in a forEach loop? I'm trying to loop through an array of files and await on the contents of each file.

    import fs from 'fs-promise'
    
    async function printFiles () {
      const files = await getFilePaths() // Assume this works fine
    
      files.forEach(async (file) => {
        const contents = await fs.readFile(file, 'utf8')
        console.log(contents)
      })
    }
    
    printFiles()
    

    This code does work, but could something go wrong with this? I had someone tell me that you're not supposed to use async/await in a higher-order function like this, so I just wanted to ask if there was any issue with this.

  • Demonbane
    Demonbane over 7 years
    Could you please explain why does for ... of ... work?
  • Demonbane
    Demonbane over 7 years
    ok i know why... Using Babel will transform async/await to generator function and using forEach means that each iteration has an individual generator function, which has nothing to do with the others. so they will be executed independently and has no context of next() with others. Actually, a simple for() loop also works because the iterations are also in one single generator function.
  • Bergi
    Bergi over 7 years
    @Demonbane: In short, because it was designed to work :-) await suspends the current function evaluation, including all control structures. Yes, it is quite similar to generators in that regard (which is why they are used to polyfill async/await).
  • arve0
    arve0 about 7 years
    So files.map(async (file) => ... is equivalent to files.map((file) => new Promise((rej, res) => { ...?
  • Bergi
    Bergi about 7 years
    @arve0 Not really, an async function is quite different from a Promise executor callback, but yes the map callback returns a promise in both cases.
  • Jay Edwards
    Jay Edwards over 6 years
    Minor addendum, don't forget to wrap your await/asyncs in try/catch blocks!!
  • Bergi
    Bergi over 6 years
    Have a look at How to define method in javascript on Array.prototype and Object.prototype so that it doesn't appear in for in loop. Also you probably should use the same iteration as native forEach - accessing indices instead of relying on iterability - and pass the index to the callback.
  • jbustamovej
    jbustamovej about 6 years
    This is a step in the wrong direction. Here's a mapping guide I created to help get folks stuck in callback hell into the modern JS era: github.com/jmjpro/async-package-to-async-await/blob/master/…‌​.
  • Zachary Ryan Smith
    Zachary Ryan Smith about 6 years
    as you can see here, I am interested in and open to using async/await instead of the async lib. Right now, I think that each has a time and place. I'm not convinced that the async lib == "callback hell" and async/await == "the modern JS era". imo, when async lib > async/await: 1. complex flow (eg, queue, cargo, even auto when things get complicated) 2. concurrency 3. supporting arrays/objects/iterables 4. err handling
  • Adi Sivasankaran
    Adi Sivasankaran about 6 years
    This answer is the best one: await Promise.all(_.map(arr, async (val) => {...}); solved my issue. Of course, each async callback returns a promise that I was not awaiting on.
  • Doug
    Doug about 6 years
    For those who don't know, Promise.all returns a single promise that resolves when all the promises in the array are resolved. (basically waits for all promises to conclude)
  • doubleOrt
    doubleOrt about 6 years
    I think the second paragraph should be "If you want to read the files in parallel, you cannot use for..of" ? Because you can read the files in parallel with the forEach, it's just that it will look very ugly because you cannot await it.
  • Bergi
    Bergi about 6 years
    @Taurus If you don't intend to await them, then for…of would work equally to forEach. No, I really mean that paragraph to emphasise that there is no place for .forEach in modern JS code.
  • doubleOrt
    doubleOrt about 6 years
    @Bergi Isn't that bad for performance ? If you need to make 3 requests, you can't make them all in parallel, so if each takes one second, your program will take 3 seconds to run. But if you use map or forEach, the requests will run in parallel.
  • Bergi
    Bergi about 6 years
    @Taurus I don't understand. What is bad for performance in comparison to what?
  • doubleOrt
    doubleOrt about 6 years
    for..of is bad for performance in comparison to forEach/map, because for..of will stop in each iteration, so if I make 3 requests, each request will have to wait for the preceding requests to complete. But with forEach/map, all the requests will be made in parallel.
  • Bergi
    Bergi about 6 years
    @Taurus No. queries.forEach(q => asyncRequest(q)); does exactly the same as for (const q of queries) asyncRequest(q);. There is no difference in performance, and both will run the requests in parallel. Of course, in neither you can wait for anything - for how to do that, see my answer.
  • doubleOrt
    doubleOrt about 6 years
  • Bergi
    Bergi about 6 years
    @Taurus Yes, that's basically the two approaches from my answer. Neither of them uses forEach. Where's the problem?
  • doubleOrt
    doubleOrt about 6 years
    @Bergi I could have used forEach (would work the same as map regarding execution time). But as you can see from my previous comments, I was talking about either forEach or map versus for..of. I said: for..of is bad for performance in comparison to forEach/map.
  • doubleOrt
    doubleOrt about 6 years
    However, how is this true (from your answer): If you want to read the files in parallel, you cannot use forEach indeed. I think you can do that, same thing as your map example, except with forEach it will look ugly and unfit for an async/await.
  • Bergi
    Bergi about 6 years
    @Taurus But that's wrong: for…of is not bad for performance in comparison to forEach if used in the same unfit way without awaiting anything. And yes, "cannot be used" means "is unfit".
  • doubleOrt
    doubleOrt about 6 years
    @Bergi Are you suggesting a case where you don't await whatever request you make inside of a for..of ? If so, alright but you do agree that your second example is way more performant than your first example ?
  • Bergi
    Bergi about 6 years
    @Taurus Yes, that's what I wrote in my comment above. Of course it doesn't make any sense and is unfit to solve the OPs problem, regardless whether with forEach or for…of. And no, I would not compare the two (for…of+await vs Promise.all+map) in terms of "performance" at all - they are just different, in many other more important regards. Choosing sequential vs parallel executing is a decision that involves many other factors, and of course parallel typically finishes faster.
  • doubleOrt
    doubleOrt about 6 years
    @Bergi Of course there are cases where the for..of might be the only way to go (e.g if each request has a dependency on the previous request), but a beginner might always opt for the for..of solution because it is simpler.
  • doubleOrt
    doubleOrt about 6 years
    @Bergi However, I still don't understand how this is correct: If you want to read the files in parallel, you cannot use forEach indeed.
  • Bergi
    Bergi about 6 years
    @Taurus You cannot use it because you cannot await the result. Of course if you used it - like the OP did - then they would run in parallel, but forEach is absolutely unfit as you said yourself.
  • Timothy Zorn
    Timothy Zorn about 6 years
    You can use Array.prototype.reduce in a way that uses an async function. I've shown an example in my answer: stackoverflow.com/a/49499491/2537258
  • parrker9
    parrker9 about 6 years
    This works perfectly, thank you so much. Could you explain what is happening here with Promise.resolve() and await promise;?
  • Ryan Bayne
    Ryan Bayne almost 6 years
    This is pretty cool. Am I right in thinking the files will be read in order and not all at once?
  • Timothy Zorn
    Timothy Zorn almost 6 years
    @parrker9 Promise.resolve() returns an already resolved Promise object, so that reduce has a Promise to start with. await promise; will wait for the last Promise in the chain to resolve. @GollyJer The files will be processed sequentially, one at a time.
  • Antonio Val
    Antonio Val over 5 years
    I don't think this answer address the initial question. for-await-of with a synchronous iterable (an array in our case) doesn’t cover the case of iterating concurrently an array using asynchronous operations in each iteration. If I'm not mistaken, using for-await-of with a synchronous iterable over non-promise values is the same as using a plain for-of.
  • Vadim Shvetsov
    Vadim Shvetsov over 5 years
    How we delegates files array to the fs.readFile here? It tooks from iterable?
  • Shay Yzhakov
    Shay Yzhakov almost 5 years
    Very cool use of reduce, thanks for the comment! I'll just denote that, in contrast to some of the other methods mentioned in the comments, this one is synchronous, meaning that the files are read one after another and not in parallel (since the next iteration of reduce function relies on the previous iteration, it must be synchronous).
  • Timothy Zorn
    Timothy Zorn almost 5 years
    @Shay, You mean sequential, not synchronous. This is still asynchronous - if other things are scheduled, they will run in between the iterations here.
  • Rafi Henig
    Rafi Henig over 4 years
    Using this solution each iteration would await for the previous, and in case of operation is making some long calculations or reading a long file it would block the executions of the next, as opposed to mapping all the functions to promises and waiting for them to complete.
  • Bergi
    Bergi over 4 years
    Your methods 1 and 2 are simply incorrect implementations where Promise.all should have been used - they do not take any of the many edge cases into account.
  • PranavKAndro
    PranavKAndro over 4 years
    @Bergi: Thanks for the valid comments, Would you please explain me why method 1 and 2 are incorrect. It also serves the purpose. This works very well. This is to say that all these methods are possible, based on the situation one can decide on choosing one. I have the running example for the same.
  • Bergi
    Bergi over 4 years
    It fails on empty arrays, it doesn't have any error handling, and probably more problems. Don't reinvent the wheel. Just use Promise.all.
  • PranavKAndro
    PranavKAndro over 4 years
    In certain conditions where its not possible it will be helpful. Also error handling is done by forEach api by default so no issues. Its taken care !
  • Bergi
    Bergi over 4 years
    No, there are no conditions where Promise.all is not possible but async/await is. And no, forEach absolutely doesn't handle any promise errors.
  • PranavKAndro
    PranavKAndro over 4 years
    Cone on Bergi, its not the matter of which is best, but the various available solutions. You mentioned error handling as empty array, empty array error handling is taken care by forEach internally. Lets drop this conversation here. Again its all the possible ways you can achieve
  • Bergi
    Bergi over 4 years
    It's not about "good" vs "better" from a pool of valid solutions, but that your code is completely broken. Have you tried what happens when you pass in an empty array? It hangs forever. Have you tried what happens when someAPICall() rejects? You get an unhandled promise rejection and it hangs forever.
  • PranavKAndro
    PranavKAndro over 4 years
    @Bergi: It is usual that if any developer use the async await to wait for the promise, then it should be surrounded by the try catch. someAPICall() is not the wrapper that I am providing, It is the developer function that developer write inside the callback that I am providing as part of wrapper, developer should surround that with try catch. If you want i can update the answer, Empty array is handled, you run and let me know. It works perfect.
  • Bergi
    Bergi over 4 years
    "developer should surround that with try catch" - and yet you didn't. If you think one always would need to do this, yes please at least do it yourself. But no, the usual expectation is that the promise returned by forEachAsync(…) should reject. And no, your code does not handle empty arrays, did you try it? await [].forEachAsync(() => {}); console.log("never happens");
  • Admin
    Admin over 4 years
    Is there a way to let this apparently fast when you have an Express page that needs a list of resources to render...?
  • Timothy Zorn
    Timothy Zorn over 4 years
    If you need the async processes to finish as quickly as possible and you don't care about them being completed sequentially, try one of the provided solutions with a good amount of upvotes which uses Promise.all. Example: Promise.all(files.map(async (file) => { /* code */ }));
  • Mark Odey
    Mark Odey almost 4 years
    The first senario is ideal for loops that needs to be ran in serie and you cant use for of
  • Bergi
    Bergi over 3 years
    Your option a involves the Promise constructor antipattern.
  • jib
    jib about 3 years
    This answer has the same issue as the OP: It accesses all files in parallel. The serialized printing of results merely hides it.
  • 010011100101
    010011100101 about 3 years
    You're a life saver. For the life of me I couldn't figure out why Object.entries({}).forEach wasn't returning the desired result. Switched it to: for (let [k, v] of Object.entries({})) {...} and it works great now. Thanks!
  • Remi Mélisson
    Remi Mélisson about 3 years
    my favorite option, I'd love to see a native Promise.chain() like all()
  • Bergi
    Bergi about 3 years
    It is also not good to open thousands of files at once to read them concurrently. One always has to do an assessment whether a sequential, parallel, or mixed approach is better. Sequential loops are not fundamentally bad, await actually makes them possible in the first place. Also they do not "aver the benefits" of asynchronous execution, as you can still run multiple such loops at once (e.g. two concurrent calls to printFiles).
  • Ray Foss
    Ray Foss about 3 years
    The main issue with this answer is that in for await, the if using a sync iterator like an Array, the map should return a promise. In node fs is not async by default... using fsPromises would make this run in parallel properly. I think its good to bring attention to this superior, well supported syntax.
  • krupesh Anadkat
    krupesh Anadkat almost 3 years
    If anyone wondering what vscode theme is that - its is github's official light theme. & If anyone hurt their eyes with so bright snapshot, my apologies 😅
  • Damien Romito
    Damien Romito almost 3 years
    usage : await myArray. forEachAsyncParallel( async (item) => { await myAsyncFunction(item) })
  • Bigyan Devkota
    Bigyan Devkota almost 3 years
    This works perfectly -- thanks, here the important step is await promise; that's what does the heavy load
  • Admin
    Admin over 2 years
    Less characters does not mean it is simpler. This is mostly convoluted and unreadable.
  • leonheess
    leonheess over 2 years
    Void function return value is used
  • Bergi
    Bergi over 2 years
    @leonheess What's the problem? Where (what code position) are you getting that warning? What tool do you use?
  • leonheess
    leonheess over 2 years
    @Bergi my bad I used forEach instead of mal
  • Griffi
    Griffi over 2 years
    the best answer ;) Useful is to just think what the transpiler is doing with async / await and how the final code will look like
  • Bergi
    Bergi over 2 years
    @Griffi Why are you still using a transpiler for async/await? And what will it transpile to?
  • srmark
    srmark over 2 years
    Not quite the same. Promise.all will run all the promises concurrently. A for loop is meant to be sequential.
  • Ido Bleicher
    Ido Bleicher over 2 years
    I think it will be helpful if you can complete this example :) in the how to use section. For my case: await asyncForEach(configuration.groupNames, async (groupName) => { await AddUsersToGroup(configuration, groupName); })
  • Bergi
    Bergi over 2 years
    Better check urls.length before calling .shift() the first time, and better use urls[0] and urls.slice(1) instead of emptying the array that is being passed to the function.
  • Bergi
    Bergi over 2 years
    Why use finally instead of then? This will ignore errors, unlike async/await
  • Matt Janssen
    Matt Janssen over 2 years
    This would be if you want to do every fetch, regardless of the success of preceding calls. Good idea on the empty check and not mutating the array! ✔
  • Jesus Loves You
    Jesus Loves You over 2 years
    best answer.....
  • Augustin Riedinger
    Augustin Riedinger over 2 years
    There should be a forEachAwait operator that does just that!
  • Bergi
    Bergi over 2 years
    @AugustinRiedinger It's called for … of!
  • Bergi
    Bergi over 2 years
    This answer is wrong. files.map() returns an array of promises, not an asynchronous iterator, for which for await was made! It will cause unhandled-rejection crashes!
  • Augustin Riedinger
    Augustin Riedinger over 2 years
    No, for ... of doesn't behave the same way: in one case, it takes a function (which can be returned, curried, composed etc.) while in the other it is simply a for loop that must be continued ...
  • Bergi
    Bergi over 2 years
    @AugustinRiedinger You cannot return anything useful from a forEach callback anyway
  • Augustin Riedinger
    Augustin Riedinger over 2 years
    Well, those don't behave the same hence it could make sense that both versions exist. Just like for ... of and forEach also coexist.
  • Bergi
    Bergi over 2 years
    OP never requested not to use async/await. They state "I'm trying to loop through an array of files and await on the contents of each file."
  • Bergi
    Bergi over 2 years
    Also, why do you say require("async").forEach only works in nodejs?
  • João Pimentel Ferreira
    João Pimentel Ferreira over 2 years
    @Bergi I explicitly said the OP didn't request exactly that and it just works with NodeJS. Although it still may be helpful for some people, because the example given by OP is to read file contents, and normally you do file reading in the backend.
  • Bergi
    Bergi over 2 years
    Oh, I misinterpreted that phrase as "does (not use async/await) as the OP requested" instead of "does not (use async/await as the OP requested)"
  • Adrian Bartholomew
    Adrian Bartholomew over 2 years
    You would need to return contents.
  • Bergi
    Bergi over 2 years
    @AdrianBartholomew OP only wants to log each file contents, and return nothing from the function. Of course if you want to create an array of file contents, you're free to return them from the map callback.
  • Andrew Smith
    Andrew Smith about 2 years
    This is extremely clever, I love it.
  • close
    close about 2 years
    I suggest using the phrase 'Before/After Loop' would make it less confusing when it's not a 'For Each Loop'.
  • JulienRioux
    JulienRioux about 2 years
    Thanks, nice solution!!
  • User_coder
    User_coder about 2 years
    The brother is out here just writing code using Githubs official like an absolute heathen. I'm not even mad. To each their own. Nonetheless, I would cache the length to speed that for loop up and prevent recalculations between every iteration.
  • ado387
    ado387 almost 2 years
    So in short: foreach doesn't handle callbacks in asynchronous way, therefore no waiting.
  • Normal
    Normal almost 2 years
    @Matt, isn't it a problem to await fn in case it wasn't asynchronous? what if the given input was a synchronous function? stackoverflow.com/a/53113299/18387350