Usando async/await con un bucle forEach

Resuelto Saad asked hace 8 años • 34 respuestas

¿Hay algún problema con el uso asyncde / awaiten un forEachbucle? Estoy intentando recorrer una serie de archivos y awaitel contenido de cada archivo.

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()

Este código funciona, pero ¿podría haber algún problema con esto? Alguien me dijo que se supone que no debes usar async/ awaiten una función de orden superior como esta, así que solo quería preguntar si había algún problema con esto.

Saad avatar Jun 02 '16 01:06 Saad
Aceptado

Seguro que el código funciona, pero estoy bastante seguro de que no hace lo que esperas. Simplemente activa múltiples llamadas asincrónicas, pero la printFilesfunción regresa inmediatamente después de eso.

Leyendo en secuencia

Si desea leer los archivos en secuencia, no puede usarlosforEach . Simplemente use un for … ofbucle moderno en su lugar, en el que awaitfuncionará como se esperaba:

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

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

Leyendo en paralelo

Si desea leer los archivos en paralelo, no puede usarlosforEach . Cada una de las asyncllamadas a la función de devolución de llamada devuelve una promesa, pero las estás desechando en lugar de esperarlas. Simplemente utilícelo mapen su lugar y podrá esperar la variedad de promesas que obtendrá con 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)
  }));
}
Bergi avatar Jun 01 '2016 19:06 Bergi

Con ES2018, podrá simplificar enormemente todas las respuestas anteriores para:

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

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

Ver especificación: propuesta-async-iteración

Simplificado:

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

10/09/2018: Esta respuesta ha recibido mucha atención recientemente; consulte la publicación del blog de Axel Rauschmayer para obtener más información sobre la iteración asincrónica.

Cisco avatar Jun 15 '2018 11:06 Cisco

En lugar de Promise.allen conjunto con Array.prototype.map(lo que no garantiza el orden en el que Promisese resuelven los s), uso Array.prototype.reduce, comenzando con un resuelto 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());
}
Timothy Zorn avatar Mar 26 '2018 19:03 Timothy Zorn