Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using async/await with a forEach loop

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.

like image 793
Saad Avatar asked Jun 01 '16 18:06

Saad


People also ask

Can you use async await in forEach?

forEach is not designed for asynchronous code. (It was not suitable for promises, and it is not suitable for async-await.)

Does forEach run async?

All in all, JavaScript forEach function executes code synchronously regardless of using it with or without the async and await keywords, which are meant to run code asynchronously. Using forEach with asynchronous code doesn't mean the code will not run. However, it will run with unexpected behaviors.

Is Lodash forEach async?

No. The JavaScript Array. prototype. forEach loop is not asynchronous.

Can we use await inside for loop?

Then the await keyword is used to block until the promise actually "resolves". So from the perspective of the for loop it will run synchronous. So: yes, the execution of the loop will be sequential.


2 Answers

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)
  }));
}
like image 162
Bergi Avatar answered Oct 17 '22 13:10

Bergi


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

Simplified:

  for await (const results of array) {
    await longRunningTask()
  }
  console.log('I will wait')

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.

like image 517
Francisco Mateo Avatar answered Oct 17 '22 14:10

Francisco Mateo