3342

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.

2
  • 2
    Gotcha! Wasted a few hours figuring this behavior out... Old fashioned iteration for i = ... never fails. Commented Mar 11, 2024 at 22:22
  • Here every time the async function returns. and the compiler can't wait for the async function till the end of the function execution that's why every async doesn't work. Commented May 6, 2024 at 12:52

35 Answers 35

1
2
1

Another issue is that using async/await inside a forEach loop, which is something we often tend to do, can lead to unintended consequences. The problem here is that forEach does not recognize async functions or handle promises correctly. Although your code may seem to "work" without throwing any errors, it won't actually await each readFile operation as intended.

Here's what happens:

The forEach loop starts all the readFile operations almost simultaneously, moving on to the next one before they finish. Since forEach doesn’t wait for the asynchronous function to complete, the printFiles function will be called before all file contents are fully read. This could cause the logs to be incomplete or lead to other issues depending on the situation.

If you need to process each file in turn, a regular for...of loop is more effective because it correctly handles await:

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

This ensures that each file is read and logged before moving on to the next one.

To sum it up, avoid using async/await inside forEach, as it may not work as expected. Instead, use a for...of the loop to guarantee proper sequential execution of asynchronous functions.

Sign up to request clarification or add additional context in comments.

Comments

0

This does not use async/await as the OP requested and only works if you are in the back-end 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.

Fully asynchronous and non-blocking:

const fs = require("fs") const async = require("async") const obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"} const configs = {} async.forEachOf(obj, (value, key, callback) => { fs.readFile(__dirname + value, "utf8", (err, data) => { if (err) return callback(err) try { configs[key] = JSON.parse(data); } catch (e) { return callback(e) } callback() }); }, err => { if (err) console.error(err.message) // configs is now a map of JSON data doSomethingWith(configs) }) 

4 Comments

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."
Also, why do you say require("async").forEach only works in nodejs?
@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.
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)"
-2

I would use the well-tested (millions of downloads per week) pify and async modules. If you are unfamiliar with the async module, I highly recommend you check out its docs. I've seen multiple devs waste time recreating its methods, or worse, making difficult-to-maintain async code when higher-order async methods would simplify code.

const async = require('async') const fs = require('fs-promise') const pify = require('pify') async function getFilePaths() { return Promise.resolve([ './package.json', './package-lock.json', ]); } async function printFiles () { const files = await getFilePaths() await pify(async.eachSeries)(files, async (file) => { // <-- run in series // await pify(async.each)(files, async (file) => { // <-- run in parallel const contents = await fs.readFile(file, 'utf8') console.log(contents) }) console.log('HAMBONE') } printFiles().then(() => { console.log('HAMBUNNY') }) // ORDER OF LOGS: // package.json contents // package-lock.json contents // HAMBONE // HAMBUNNY ```

2 Comments

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/….
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
-2

In 2022 I would still advise using external libraries to handle all this async flow. I've created the module alot🔗 for similar things.

Your example would be:

import fs from 'fs-promise' import alot from 'alot' async function printFiles () { const files = await getFilePaths() // Assume this works fine await alot(files) .forEachAsync(async file => { let content = await fs.readFile(file, 'utf8'); console.log(content); }) .toArrayAsync({ threads: 4 }); } } printFiles() 

For simple examples surely the async for..of would do the job, but as soon the task is more complicated you have to use some utility for this.

Alot has dozens of other methods that you can chain, like mapAsync, filterAsync, groupAsync, etc.

As an example:

  • Load JSON files with products meta
  • Extract ProductID
  • Load products from the server
  • Filter those with a price > 100$
  • Order by price ascending
  • Take top 50

import fs from 'fs-promise' import alot from 'alot' import axios from 'axios' import { File } from 'atma-io' let paths = await getFilePaths(); let products = await alot(paths) .mapAsync(async path => await File.readAsync<IProductMeta>(path)) .mapAsync(async meta => await axios.get(`${server}/api/product/${meta.productId}`)) .mapAsync(resp => resp.data) .filterAsync(product => product.price > 100) .sortBy(product => product.price, 'asc') .takeAsync(50) .toArrayAsync({ threads: 5, errors: 'include' }); 

2 Comments

What is threads: 4? JS doesn't have threads
@Bergi But the underlying layer has. All this async\await story means the event-loop waits until it gets the result back. By defining threads we set how many tasks we start parallel, other will wait until at least on task (fs, network, worker, etc.) is ready.
-5

You can use the async.forEach loop from the async package:

async.forEach(dataToLoop(array), async(data, cb) => { variable = await MongoQuery; }, function(err) { console.log(err); }) }) .catch((err)=>{ console.log(err); }) 

Comments

1
2

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.