Usar async / await con un bucle forEach


1133

¿Hay algún problema con el uso de async/ awaiten un forEachbucle? Estoy tratando de recorrer una variedad 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 ¿algo podría salir mal 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.

Respuestas:


2158

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

Lectura en secuencia

Si desea leer los archivos en secuencia, no puede usarlosforEach . Simplemente use un for … ofbucle moderno , 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 descarta en lugar de esperarlas. Simplemente use mapen su lugar, y puede 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)
  }));
}

33
¿Podría explicar por qué for ... of ...funciona?
Demonbane

84
ok, sé por qué ... Usar Babel transformará async/ awaita la función de generador y usar forEachsignifica que cada iteración tiene una función de generador individual, que no tiene nada que ver con las otras. entonces serán ejecutados independientemente y no tienen contexto next()con otros. En realidad, un for()bucle simple también funciona porque las iteraciones también están en una sola función de generador.
Demonbane

21
@Demonbane: En resumen, porque fue diseñado para funcionar :-) awaitsuspende la evaluación de la función actual , incluidas todas las estructuras de control. Sí, es bastante similar a los generadores en ese sentido (razón por la cual se usan para polifiliar asíncrono / esperar).
Bergi

3
@ arve0 En realidad no, una asyncfunción es bastante diferente de una Promisedevolución de llamada ejecutora, pero sí, la mapdevolución de llamada devuelve una promesa en ambos casos.
Bergi

55
Cuando vengas a aprender sobre las promesas de JS, en lugar de eso usa media hora traduciendo latín. Espero que estés orgulloso @Bergi;)
Félix Gagnon-Grenier

190

Con ES2018, puede simplificar enormemente todas las respuestas anteriores a:

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

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

Ver especificación: propuesta-iteración-asíncrona


2018-09-10: 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: ES2018: iteración asincrónica


44
Votado, sería genial si pudieras poner un enlace a la especificación en tu respuesta para cualquiera que quiera saber más sobre la iteración asíncrona.
saadq

8
¿No debería ser contenido en lugar de archivo en el iterador
FluffyBeing

10
¿Por qué la gente está votando esta respuesta? Eche un vistazo más de cerca a la respuesta, pregunta y propuesta. Después ofdebería ser la función asíncrona que devolverá una matriz. No funciona y Francisco dijo;
Yevhenii Herasymchuk

3
Totalmente de acuerdo con @AntonioVal. No es una respuesta.
Yevhenii Herasymchuk

2
Si bien estoy de acuerdo en que no es una respuesta, votar por una propuesta es una forma de aumentar su popularidad, potencialmente haciéndola disponible antes para usarla más adelante.
Robert Molina

62

En lugar de Promise.allen conjunto con Array.prototype.map(que no garantiza el orden en 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());
}

1
Esto funciona perfectamente, muchas gracias. ¿Podría explicar qué está pasando aquí con Promise.resolve()y await promise;?
parrker9

1
Esto está muy bien. ¿Estoy en lo cierto al pensar que los archivos se leerán en orden y no todos a la vez?
GollyJer

1
@ parrker9 Promise.resolve()devuelve un Promiseobjeto ya resuelto , por lo que reducetiene Promiseque comenzar. await promise;esperará Promisea que se resuelva el último de la cadena. @GollyJer Los archivos se procesarán secuencialmente, uno a la vez.
Timothy Zorn

Muy buen uso de reducir, gracias por el comentario! Solo denotaré que, en contraste con algunos de los otros métodos mencionados en los comentarios, este es sincrónico, lo que significa que los archivos se leen uno tras otro y no en paralelo (ya que la próxima iteración de la función reducir se basa en la anterior iteración, debe ser síncrono).
Shay Yzhakov,

1
@ Shay, te refieres a secuencial, no sincrónico. Esto sigue siendo asíncrono: si hay otras cosas programadas, se ejecutarán entre las iteraciones aquí.
Timothy Zorn

33

El módulo p- iteration en npm implementa los métodos de iteración Array para que puedan usarse de una manera muy directa con async / await.

Un ejemplo con su caso:

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

1
Me gusta esto, ya que tiene las mismas funciones / métodos que JS en sí mismo, en mi caso, necesitaba somemás que forEach. ¡Gracias!
mikemaccana

26

Aquí hay algunos forEachAsyncprototipos. Tenga en cuenta que los necesitará await:

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));
}

Tenga en cuenta que si bien puede incluir esto en su propio código, no debe incluirlo en las bibliotecas que distribuye a otros (para evitar contaminar sus globales).


1
Aunque dudaría en agregar cosas directamente al prototipo, este es un buen asíncrono para cada implementación
DaniOcean

2
Mientras el nombre sea único en el futuro (como yo usaría _forEachAsync), esto es razonable. También creo que es la mejor respuesta, ya que ahorra mucho código repetitivo.
mikemaccana

1
@estus Eso es para evitar contaminar el código de otras personas. Si el código pertenece a nuestra organización personal, y los globales están en un archivo bien identificado ( globals.jssería bueno), podemos agregar globales como deseemos.
mikemaccana

1
@mikemaccana Eso es para evitar las malas prácticas generalmente aceptadas. Eso es cierto, esto se puede hacer siempre que use solo código propio, lo que ocurre raramente. El problema es que cuando usas librerías de terceros, puede haber algún otro tipo que sienta lo mismo y modifique las mismas variables globales, solo porque parecía una buena idea en el momento en que se escribió una lib.
Estus Flask

1
@estus Claro. He agregado una advertencia a la pregunta para guardar la discusión (no particularmente productiva) aquí.
mikemaccana

7

Además de la respuesta de @ Bergi , me gustaría ofrecer una tercera alternativa. Es muy similar al segundo ejemplo de @ Bergi, pero en lugar de esperar cada uno readFileindividualmente, crea una serie de promesas, cada una de las cuales espera al final.

import fs from 'fs-promise';
async function printFiles () {
  const files = await getFilePaths();

  const promises = files.map((file) => fs.readFile(file, 'utf8'))

  const contents = await Promise.all(promises)

  contents.forEach(console.log);
}

Tenga en cuenta que la función pasada a .map()no necesita ser async, ya fs.readFileque de todos modos devuelve un objeto Promise. Por lo tanto promiseses una matriz de objetos prometen, que pueden ser enviados a Promise.all().

En la respuesta de @ Bergi, la consola puede registrar el contenido del archivo en el orden en que se leen. Por ejemplo, si un archivo realmente pequeño termina de leerse antes de un archivo realmente grande, se registrará primero, incluso si el archivo pequeño viene después del archivo grande en la filesmatriz. Sin embargo, en mi método anterior, tiene la garantía de que la consola registrará los archivos en el mismo orden que la matriz proporcionada.


1
Estoy bastante seguro de que eres incorrecto: estoy bastante seguro de que tu método también puede leer los archivos fuera de servicio. Sí, registrará la salida en el orden correcto (debido a await Promise.all), pero los archivos pueden haber sido leídos en un orden diferente, lo que contradice su declaración "está garantizado que la consola registrará los archivos en el mismo orden en que están". leer".
Venryx

1
@Venryx Tienes razón, gracias por la corrección. He actualizado mi respuesta.
chharvey

6

La solución de Bergi funciona bien cuando fsse basa en promesas. Puedes usar bluebird, fs-extrao fs-promisepara esto.

Sin embargo, la solución para la fsbiblioteca nativa del nodo es la siguiente:

const result = await Promise.all(filePaths
    .map( async filePath => {
      const fileContents = await getAssetFromCache(filePath, async function() {

        // 1. Wrap with Promise    
        // 2. Return the result of the Promise
        return await new Promise((res, rej) => {
          fs.readFile(filePath, 'utf8', function(err, data) {
            if (data) {
              res(data);
            }
          });
        });
      });

      return fileContents;
    }));

Nota: require('fs') obligatoriamente toma la función como tercer argumento, de lo contrario arroja un error:

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function

5

Ambas soluciones anteriores funcionan, sin embargo, Antonio hace el trabajo con menos código, así es como me ayudó a resolver los datos de mi base de datos, de varias referencias de niños diferentes y luego empujarlos a todos en una matriz y resolverlo en una promesa después de todo. hecho:

Promise.all(PacksList.map((pack)=>{
    return fireBaseRef.child(pack.folderPath).once('value',(snap)=>{
        snap.forEach( childSnap => {
            const file = childSnap.val()
            file.id = childSnap.key;
            allItems.push( file )
        })
    })
})).then(()=>store.dispatch( actions.allMockupItems(allItems)))

4

es bastante sencillo introducir un par de métodos en un archivo que manejará datos asincrónicos en un orden serializado y le dará un sabor más convencional a su código. Por ejemplo:

module.exports = function () {
  var self = this;

  this.each = async (items, fn) => {
    if (items && items.length) {
      await Promise.all(
        items.map(async (item) => {
          await fn(item);
        }));
    }
  };

  this.reduce = async (items, fn, initialValue) => {
    await self.each(
      items, async (item) => {
        initialValue = await fn(initialValue, item);
      });
    return initialValue;
  };
};

ahora, suponiendo que esté guardado en './myAsync.js', puede hacer algo similar a lo siguiente en un archivo adyacente:

...
/* your server setup here */
...
var MyAsync = require('./myAsync');
var Cat = require('./models/Cat');
var Doje = require('./models/Doje');
var example = async () => {
  var myAsync = new MyAsync();
  var doje = await Doje.findOne({ name: 'Doje', noises: [] }).save();
  var cleanParams = [];

  // FOR EACH EXAMPLE
  await myAsync.each(['bork', 'concern', 'heck'], 
    async (elem) => {
      if (elem !== 'heck') {
        await doje.update({ $push: { 'noises': elem }});
      }
    });

  var cat = await Cat.findOne({ name: 'Nyan' });

  // REDUCE EXAMPLE
  var friendsOfNyanCat = await myAsync.reduce(cat.friends,
    async (catArray, friendId) => {
      var friend = await Friend.findById(friendId);
      if (friend.name !== 'Long cat') {
        catArray.push(friend.name);
      }
    }, []);
  // Assuming Long Cat was a friend of Nyan Cat...
  assert(friendsOfNyanCat.length === (cat.friends.length - 1));
}

2
Addendum menor, ¡¡no te olvides de envolver tus esperar / asincs en bloques try / catch !!
Jay Edwards

4

Como la respuesta de @ Bergi, pero con una diferencia.

Promise.all rechaza todas las promesas si uno es rechazado.

Entonces, usa una recursión.

const readFilesQueue = async (files, index = 0) {
    const contents = await fs.readFile(files[index], 'utf8')
    console.log(contents)

    return files.length <= index
        ? readFilesQueue(files, ++index)
        : files

}

const printFiles async = () => {
    const files = await getFilePaths();
    const printContents = await readFilesQueue(files)

    return printContents
}

printFiles()

PD

readFilesQueue está fuera de printFiles causa del efecto secundario * introducido por console.log, es mejor burlarse, probar o espiar, por lo que no es bueno tener una función que devuelva el contenido (nota al margen).

Por lo tanto, el código puede ser diseñado simplemente por eso: tres funciones separadas que son "puras" ** y no presentan efectos secundarios, procesan la lista completa y pueden modificarse fácilmente para manejar casos fallidos.

const files = await getFilesPath()

const printFile = async (file) => {
    const content = await fs.readFile(file, 'utf8')
    console.log(content)
}

const readFiles = async = (files, index = 0) => {
    await printFile(files[index])

    return files.lengh <= index
        ? readFiles(files, ++index)
        : files
}

readFiles(files)

Edición futura / estado actual

Node admite espera de nivel superior (esto aún no tiene un complemento, no lo tendrá y se puede habilitar a través de indicadores de armonía), es genial pero no resuelve un problema (estratégicamente solo trabajo en versiones LTS). ¿Cómo obtener los archivos?

Usando la composición. Dado el código, me causa la sensación de que esto está dentro de un módulo, por lo tanto, debería tener una función para hacerlo. Si no, debe usar un IIFE para envolver el código de rol en una función asíncrona creando un módulo simple que haga todo por usted, o puede ir de la manera correcta, hay, composición.

// more complex version with IIFE to a single module
(async (files) => readFiles(await files())(getFilesPath)

Tenga en cuenta que el nombre de las variables cambia debido a la semántica. Pasa un functor (una función que puede ser invocada por otra función) y recibe un puntero en la memoria que contiene el bloque inicial de lógica de la aplicación.

Pero, si no es un módulo y necesita exportar la lógica?

Envuelva las funciones en una función asíncrona.

export const readFilesQueue = async () => {
    // ... to code goes here
}

O cambiar los nombres de las variables, lo que sea ...


* por efecto secundario significa cualquier efecto colacteral de la aplicación que puede cambiar el estado / comportamiento o introducir errores en la aplicación, como IO.

** por "puro", está en apóstrofo ya que las funciones no son puras y el código puede converger a una versión pura, cuando no hay salida de consola, solo manipulaciones de datos.

Aparte de esto, para ser puro, necesitará trabajar con mónadas que manejan el efecto secundario, que son propensas a errores, y tratan ese error por separado de la aplicación.


3

Una advertencia importante es: el await + for .. ofmétodo y la forEach + asyncforma en realidad tienen un efecto diferente.

Tener awaitun forbucle real se asegurará de que todas las llamadas asíncronas se ejecuten una por una. Y la forEach + asyncforma disparará todas las promesas al mismo tiempo, lo que es más rápido pero a veces abrumado ( si realiza alguna consulta de base de datos o visita algunos servicios web con restricciones de volumen) y no desea disparar 100,000 llamadas a la vez).

También puede usar reduce + promise(menos elegante) si no lo usa async/awaity quiere asegurarse de que los archivos se lean uno tras otro .

files.reduce((lastPromise, file) => 
 lastPromise.then(() => 
   fs.readFile(file, 'utf8')
 ), Promise.resolve()
)

O puede crear un forEachAsync para ayudar, pero básicamente use el mismo para el bucle subyacente.

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}

Eche un vistazo a Cómo definir el método en javascript en Array.prototype y Object.prototype para que no aparezca en bucle . Además, probablemente debería usar la misma iteración que la nativa forEach(acceder a los índices en lugar de confiar en la iterabilidad) y pasar el índice a la devolución de llamada.
Bergi

Puede usar Array.prototype.reducede una manera que use una función asíncrona. He mostrado un ejemplo en mi respuesta: stackoverflow.com/a/49499491/2537258
Timothy Zorn el

3

Usando Task, futurize y una Lista transitable, simplemente puedes hacer

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

  List(files).traverse( Task.of, f => readFile( f, 'utf-8'))
    .fork( console.error, console.log)
}

Así es como configurarías esto

import fs from 'fs';
import { futurize } from 'futurize';
import Task from 'data.task';
import { List } from 'immutable-ext';

const future = futurizeP(Task)
const readFile = future(fs.readFile)

Otra forma de estructurar el código deseado sería

const printFiles = files => 
  List(files).traverse( Task.of, fn => readFile( fn, 'utf-8'))
    .fork( console.error, console.log)

O tal vez incluso más funcionalmente orientado

// 90% of encodings are utf-8, making that use case super easy is prudent

// handy-library.js
export const readFile = f =>
  future(fs.readFile)( f, 'utf-8' )

export const arrayToTaskList = list => taskFn => 
  List(files).traverse( Task.of, taskFn ) 

export const readFiles = files =>
  arrayToTaskList( files, readFile )

export const printFiles = files => 
  readFiles(files).fork( console.error, console.log)

Luego de la función padre

async function main() {
  /* awesome code with side-effects before */
  printFiles( await getFiles() );
  /* awesome code with side-effects after */
}

Si realmente quisiera más flexibilidad en la codificación, podría hacerlo (por diversión, estoy usando el operador Pipe Forward propuesto )

import { curry, flip } from 'ramda'

export const readFile = fs.readFile 
  |> future,
  |> curry,
  |> flip

export const readFileUtf8 = readFile('utf-8')

PD: no probé este código en la consola, podría tener algunos errores tipográficos ... "¡estilo libre directo, desde la parte superior del domo!" como dirían los niños de los 90. :-pags


3

Actualmente, la propiedad prototipo Array.forEach no admite operaciones asíncronas, pero podemos crear nuestro propio poly-fill para satisfacer nuestras necesidades.

// Example of asyncForEach Array poly-fill for NodeJs
// file: asyncForEach.js
// Define asynForEach function 
async function asyncForEach(iteratorFunction){
  let indexer = 0
  for(let data of this){
    await iteratorFunction(data, indexer)
    indexer++
  }
}
// Append it as an Array prototype property
Array.prototype.asyncForEach = asyncForEach
module.exports = {Array}

¡Y eso es! Ahora tiene un asíncrono para cada método disponible en cualquier matriz que se defina después de estas operaciones.

Probémoslo ...

// Nodejs style
// file: someOtherFile.js

const readline = require('readline')
Array = require('./asyncForEach').Array
const log = console.log

// Create a stream interface
function createReader(options={prompt: '>'}){
  return readline.createInterface({
    input: process.stdin
    ,output: process.stdout
    ,prompt: options.prompt !== undefined ? options.prompt : '>'
  })
}
// Create a cli stream reader
async function getUserIn(question, options={prompt:'>'}){
  log(question)
  let reader = createReader(options)
  return new Promise((res)=>{
    reader.on('line', (answer)=>{
      process.stdout.cursorTo(0, 0)
      process.stdout.clearScreenDown()
      reader.close()
      res(answer)
    })
  })
}

let questions = [
  `What's your name`
  ,`What's your favorite programming language`
  ,`What's your favorite async function`
]
let responses = {}

async function getResponses(){
// Notice we have to prepend await before calling the async Array function
// in order for it to function as expected
  await questions.asyncForEach(async function(question, index){
    let answer = await getUserIn(question)
    responses[question] = answer
  })
}

async function main(){
  await getResponses()
  log(responses)
}
main()
// Should prompt user for an answer to each question and then 
// log each question and answer as an object to the terminal

Podríamos hacer lo mismo para algunas de las otras funciones de matriz como map ...

async function asyncMap(iteratorFunction){
  let newMap = []
  let indexer = 0
  for(let data of this){
    newMap[indexer] = await iteratorFunction(data, indexer, this)
    indexer++
  }
  return newMap
}

Array.prototype.asyncMap = asyncMap

... y así :)

Algunas cosas a tener en cuenta:

  • Su iteratorFunction debe ser una función asincrónica o promesa
  • Cualquier matriz creada anteriormente Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>no tendrá esta función disponible

3

Solo agregando a la respuesta original

  • La sintaxis de lectura paralela en la respuesta original a veces es confusa y difícil de leer, tal vez podamos escribirla con un enfoque diferente
async function printFiles() {
  const files = await getFilePaths();
  const fileReadPromises = [];

  const readAndLogFile = async filePath => {
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
    return contents;
  };

  files.forEach(file => {
    fileReadPromises.push(readAndLogFile(file));
  });

  await Promise.all(fileReadPromises);
}
  • Para la operación secuencial, no solo para ... of , normal for loop también funcionará
async function printFiles() {
  const files = await getFilePaths();

  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
  }
}

2

Para ver cómo puede salir mal, imprima console.log al final del método.

Cosas que pueden salir mal en general:

  • Orden arbitrario.
  • printFiles puede terminar de ejecutarse antes de imprimir archivos.
  • Bajo rendimiento.

Estos no siempre son incorrectos, pero con frecuencia están en casos de uso estándar.

En general, el uso de forEach dará como resultado todos menos el último. Llamará a cada función sin esperar a la función, lo que significa que le dice a todas las funciones que comiencen y luego finaliza sin esperar a que las funciones finalicen.

import fs from 'fs-promise'

async function printFiles () {
  const files = (await getFilePaths()).map(file => fs.readFile(file, 'utf8'))

  for(const file of files)
    console.log(await file)
}

printFiles()

Este es un ejemplo en JS nativo que preservará el orden, evitará que la función regrese prematuramente y en teoría retendrá un rendimiento óptimo.

Esta voluntad:

  • Inicie todas las lecturas de archivos para que sucedan en paralelo.
  • Preserve el orden mediante el uso de map para asignar nombres de archivos a promesas que esperar.
  • Espere cada promesa en el orden definido por la matriz.

Con esta solución, el primer archivo se mostrará tan pronto como esté disponible sin tener que esperar a que los otros estén disponibles primero.

También cargará todos los archivos al mismo tiempo en lugar de tener que esperar a que termine el primero antes de que se pueda iniciar la lectura del segundo archivo.

El único inconveniente de esto y de la versión original es que si se inician varias lecturas a la vez, entonces es más difícil manejar los errores debido a que puede haber más errores a la vez.

Con versiones que leen un archivo a la vez, entonces se detendrán ante un fallo sin perder tiempo tratando de leer más archivos. Incluso con un elaborado sistema de cancelación, puede ser difícil evitar que falle en el primer archivo pero también leer la mayoría de los otros archivos.

El rendimiento no siempre es predecible. Si bien muchos sistemas serán más rápidos con lecturas paralelas de archivos, algunos preferirán secuenciales. Algunos son dinámicos y pueden cambiar bajo carga, las optimizaciones que ofrecen latencia no siempre producen un buen rendimiento bajo una fuerte disputa.

Tampoco hay manejo de errores en ese ejemplo. Si algo requiere que se muestren correctamente o no se muestre, no lo hará.

Se recomienda experimentar en profundidad con console.log en cada etapa y soluciones de lectura de archivos falsos (en su lugar, retraso aleatorio). Aunque muchas soluciones parecen hacer lo mismo en casos simples, todas tienen diferencias sutiles que requieren un escrutinio adicional para exprimirse.

Use este simulacro para ayudar a distinguir las soluciones:

(async () => {
  const start = +new Date();
  const mock = () => {
    return {
      fs: {readFile: file => new Promise((resolve, reject) => {
        // Instead of this just make three files and try each timing arrangement.
        // IE, all same, [100, 200, 300], [300, 200, 100], [100, 300, 200], etc.
        const time = Math.round(100 + Math.random() * 4900);
        console.log(`Read of ${file} started at ${new Date() - start} and will take ${time}ms.`)
        setTimeout(() => {
          // Bonus material here if random reject instead.
          console.log(`Read of ${file} finished, resolving promise at ${new Date() - start}.`);
          resolve(file);
        }, time);
      })},
      console: {log: file => console.log(`Console Log of ${file} finished at ${new Date() - start}.`)},
      getFilePaths: () => ['A', 'B', 'C', 'D', 'E']
    };
  };

  const printFiles = (({fs, console, getFilePaths}) => {
    return async function() {
      const files = (await getFilePaths()).map(file => fs.readFile(file, 'utf8'));

      for(const file of files)
        console.log(await file);
    };
  })(mock());

  console.log(`Running at ${new Date() - start}`);
  await printFiles();
  console.log(`Finished running at ${new Date() - start}`);
})();

2

Hoy encontré múltiples soluciones para esto. Ejecutar las funciones de espera asíncrona en forEach Loop. Al construir el envoltorio alrededor podemos hacer que esto suceda.

Una explicación más detallada sobre cómo funciona internamente, para el nativo de ForEach y por qué no puede realizar una llamada de función asíncrona, y se proporcionan otros detalles sobre los diversos métodos en el enlace aquí.

Las múltiples formas a través de las cuales se puede hacer y son las siguientes,

Método 1: usar el envoltorio.

await (()=>{
     return new Promise((resolve,reject)=>{
       items.forEach(async (item,index)=>{
           try{
               await someAPICall();
           } catch(e) {
              console.log(e)
           }
           count++;
           if(index === items.length-1){
             resolve('Done')
           }
         });
     });
    })();

Método 2: usar lo mismo que una función genérica de Array.prototype

Array.prototype.forEachAsync.js

if(!Array.prototype.forEachAsync) {
    Array.prototype.forEachAsync = function (fn){
      return new Promise((resolve,reject)=>{
        this.forEach(async(item,index,array)=>{
            await fn(item,index,array);
            if(index === array.length-1){
                resolve('done');
            }
        })
      });
    };
  }

Uso:

require('./Array.prototype.forEachAsync');

let count = 0;

let hello = async (items) => {

// Method 1 - Using the Array.prototype.forEach 

    await items.forEachAsync(async () => {
         try{
               await someAPICall();
           } catch(e) {
              console.log(e)
           }
        count++;
    });

    console.log("count = " + count);
}

someAPICall = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("done") // or reject('error')
        }, 100);
    })
}

hello(['', '', '', '']); // hello([]) empty array is also be handled by default

Método 3:

Usando Promise.all

  await Promise.all(items.map(async (item) => {
        await someAPICall();
        count++;
    }));

    console.log("count = " + count);

Método 4: tradicional para bucle o moderno para bucle

// Method 4 - using for loop directly

// 1. Using the modern for(.. in..) loop
   for(item in items){

        await someAPICall();
        count++;
    }

//2. Using the traditional for loop 

    for(let i=0;i<items.length;i++){

        await someAPICall();
        count++;
    }


    console.log("count = " + count);

Sus métodos 1 y 2 son simplemente implementaciones incorrectas donde Promise.alldeberían haberse utilizado: no tienen en cuenta ninguno de los muchos casos extremos.
Bergi

@ Bergi: Gracias por los comentarios válidos. ¿Podría explicarme por qué los métodos 1 y 2 son incorrectos? También sirve el propósito. Esto funciona muy bien Esto quiere decir que todos estos métodos son posibles, en función de la situación en la que uno puede decidir elegir uno. Tengo el ejemplo de ejecución para el mismo.
PranavKAndro

Falla en matrices vacías, no tiene ningún manejo de errores, y probablemente más problemas. No reinventes la rueda. Solo úsalo Promise.all.
Bergi

En ciertas condiciones donde no es posible, será útil. También el manejo de errores se realiza por cada API por defecto, así que no hay problemas. ¡Está cuidado!
PranavKAndro

No, no hay condiciones donde Promise.allno es posible pero async/ awaites. Y no, forEachabsolutamente no maneja ningún error de promesa.
Bergi

2

Esta solución también está optimizada para la memoria, por lo que puede ejecutarla en 10,000 de elementos de datos y solicitudes. Algunas de las otras soluciones aquí bloquearán el servidor en grandes conjuntos de datos.

En TypeScript:

export async function asyncForEach<T>(array: Array<T>, callback: (item: T, index: number) => void) {
        for (let index = 0; index < array.length; index++) {
            await callback(array[index], index);
        }
    }

¿Cómo utilizar?

await asyncForEach(receipts, async (eachItem) => {
    await ...
})

2

Puede usar Array.prototype.forEach, pero async / await no es tan compatible. Esto se debe a que la promesa devuelta de una devolución de llamada asincrónica espera resolverse, peroArray.prototype.forEach no resuelve ninguna promesa de la ejecución de su devolución de llamada. Entonces, puede usar forEach, pero tendrá que manejar la resolución de la promesa usted mismo.

Aquí hay una manera de leer e imprimir cada archivo en serie usando Array.prototype.forEach

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

  let promiseChain = Promise.resolve()
  files.forEach((file) => {
    promiseChain = promiseChain.then(() => {
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    })
  })
  await promiseChain
}

Aquí hay una manera (aún en uso Array.prototype.forEach) para imprimir el contenido de los archivos en paralelo

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

  const promises = []
  files.forEach((file) => {
    promises.push(
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    )
  })
  await Promise.all(promises)
}

El primer senario es ideal para bucles que deben ejecutarse en serie y no se pueden usar para
Mark Odey

0

Similar a Antonio Val p-iteration, un módulo npm alternativo es async-af:

const AsyncAF = require('async-af');
const fs = require('fs-promise');

function printFiles() {
  // since AsyncAF accepts promises or non-promises, there's no need to await here
  const files = getFilePaths();

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

printFiles();

Alternativamente, async-aftiene un método estático (log / logAF) que registra los resultados de las promesas:

const AsyncAF = require('async-af');
const fs = require('fs-promise');

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

  AsyncAF(files).forEach(file => {
    AsyncAF.log(fs.readFile(file, 'utf8'));
  });
}

printFiles();

Sin embargo, la principal ventaja de la biblioteca es que puede encadenar métodos asincrónicos para hacer algo como:

const aaf = require('async-af');
const fs = require('fs-promise');

const printFiles = () => aaf(getFilePaths())
  .map(file => fs.readFile(file, 'utf8'))
  .forEach(file => aaf.log(file));

printFiles();

async-af


-3

Usaría los módulos pify y asíncronos bien probados (millones de descargas por semana) . Si no está familiarizado con el módulo asíncrono, le recomiendo que consulte sus documentos . He visto a múltiples desarrolladores perder el tiempo recreando sus métodos, o peor, haciendo que el código asincrónico sea difícil de mantener cuando los métodos asincrónicos de orden superior simplificarían el código.

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
```


Este es un paso en la dirección equivocada. Aquí hay una guía de mapeo que creé para ayudar a que la gente se quede atrapada en el infierno de devolución de llamadas en la era moderna de JS: github.com/jmjpro/async-package-to-async-await/blob/master/… .
jbustamovej

Como puede ver aquí , estoy interesado y abierto a usar async / await en lugar de async lib. En este momento, creo que cada uno tiene un tiempo y un lugar. No estoy convencido de que el async lib == "callback hell" y async / await == "la era moderna de JS". OMI, cuando async lib> async / await: 1. flujo complejo (por ejemplo, cola, carga, incluso automático cuando las cosas se complican) 2. concurrencia 3. matrices / objetos / iterables compatibles 4. manejo de errores
Zachary Ryan Smith
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.