then nodejs loop for await async javascript node.js promise async-await ecmascript-2017

javascript - nodejs - Usar async/await con un bucle forEach



javascript await for (14)

¿Hay algún problema con el uso de async / forEach en un bucle forEach ? Estoy tratando de recorrer un conjunto de archivos y await el 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? Le pedí a alguien que me dijera que no debes usar async / await en una función de orden superior como esta, así que solo quería preguntar si había algún problema con esto.


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 antes de Array.prototype.<yourAsyncFunc> = <yourAsyncFunc> no tendrá esta característica disponible

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 readFile individualmente, 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 que fs.readFile devuelve un objeto Promise de todos modos. Por lo tanto, promises es una matriz de objetos Promise, que se pueden enviar a Promise.all() .

En la respuesta de @ Bergi, la consola puede registrar el contenido del archivo fuera de servicio. 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 matriz de files . Sin embargo, en mi método anterior, tiene la garantía de que la consola registrará los archivos en el mismo orden en que se leen.


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


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: https://github.com/tc39/proposal-async-iteration

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: http://2ality.com/2016/10/asynchronous-iteration.html


El módulo p-iteration iteration en npm implementa los métodos de iteración de 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); }); })();


En lugar de Promise.all junto con Array.prototype.map (que no garantiza el orden en que se resuelven las Promesas), utilizo Array.prototype.reduce , comenzando con una Promise resuelta:

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


Estos son algunos de los prototipos de cada uno. Tenga en cuenta que deberá 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).


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 función printFiles regresa inmediatamente después de eso.

Si desea leer los archivos en secuencia, no puede usar forEach hecho. Simplemente use un moderno for … of bucle, en el que await funcionará 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); } }

Si desea leer los archivos en paralelo, no puede usar forEach hecho. Cada una de las llamadas a la función de devolución de llamada async devuelve una promesa, pero las descarta en lugar de esperarlas. Simplemente use map 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) })); }


Similar a la p-iteration Antonio Val, 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-af tiene 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


Una advertencia importante es: El método forEach + async await + for .. of y la forma forEach + async realidad tienen un efecto diferente.

await dentro de un bucle real se asegurará de que todas las llamadas asíncronas se ejecuten una por una. Y la forma forEach + async 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 usa async/await y desea 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); } }


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


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


La solución de Bergi funciona bien cuando fs se basa en promesas. Puede usar bluebird , fs-extra o fs-promise para esto.

Sin embargo, la solución para la biblioteca fs 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


pify módulos pify y async 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 aún, 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 ```