¿Hay algún problema con el uso de async
/ await
en un forEach
bucle? Estoy tratando de recorrer una variedad 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? Alguien me dijo que se supone 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.
for ... of ...
funciona?async
/await
a la función de generador y usarforEach
significa 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 contextonext()
con otros. En realidad, unfor()
bucle simple también funciona porque las iteraciones también están en una sola función de generador.await
suspende 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).async
función es bastante diferente de unaPromise
devolución de llamada ejecutora, pero sí, lamap
devolución de llamada devuelve una promesa en ambos casos.Con ES2018, puede simplificar enormemente todas las respuestas anteriores a:
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
fuente
of
debería ser la función asíncrona que devolverá una matriz. No funciona y Francisco dijo;En lugar de
Promise.all
en conjunto conArray.prototype.map
(que no garantiza el orden en quePromise
se resuelven los s), usoArray.prototype.reduce
, comenzando con un resueltoPromise
:fuente
Promise.resolve()
yawait promise;
?Promise.resolve()
devuelve unPromise
objeto ya resuelto , por lo quereduce
tienePromise
que comenzar.await promise;
esperaráPromise
a que se resuelva el último de la cadena. @GollyJer Los archivos se procesarán secuencialmente, uno a la vez.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:
fuente
some
más queforEach
. ¡Gracias!Aquí hay algunos
forEachAsync
prototipos. Tenga en cuenta que los necesitaráawait
: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).
fuente
_forEachAsync
), esto es razonable. También creo que es la mejor respuesta, ya que ahorra mucho código repetitivo.globals.js
sería bueno), podemos agregar globales como deseemos.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
readFile
individualmente, crea una serie de promesas, cada una de las cuales espera al final.Tenga en cuenta que la función pasada a
.map()
no necesita serasync
, yafs.readFile
que de todos modos devuelve un objeto Promise. Por lo tantopromises
es una matriz de objetos prometen, que pueden ser enviados aPromise.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
files
matriz. 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.fuente
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".La solución de Bergi funciona bien cuando
fs
se basa en promesas. Puedes usarbluebird
,fs-extra
ofs-promise
para esto.Sin embargo, la solución para la
fs
biblioteca nativa del nodo es la siguiente:Nota:
require('fs')
obligatoriamente toma la función como tercer argumento, de lo contrario arroja un error:fuente
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:
fuente
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:
ahora, suponiendo que esté guardado en './myAsync.js', puede hacer algo similar a lo siguiente en un archivo adyacente:
fuente
Como la respuesta de @ Bergi, pero con una diferencia.
Promise.all
rechaza todas las promesas si uno es rechazado.Entonces, usa una recursión.
PD
readFilesQueue
está fuera deprintFiles
causa del efecto secundario * introducido porconsole.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.
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.
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.
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.
fuente
Una advertencia importante es: el
await + for .. of
método y laforEach + async
forma en realidad tienen un efecto diferente.Tener
await
unfor
bucle real se asegurará de que todas las llamadas asíncronas se ejecuten una por una. Y laforEach + async
forma 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 usaasync/await
y quiere asegurarse de que los archivos se lean uno tras otro .O puede crear un forEachAsync para ayudar, pero básicamente use el mismo para el bucle subyacente.
fuente
forEach
(acceder a los índices en lugar de confiar en la iterabilidad) y pasar el índice a la devolución de llamada.Array.prototype.reduce
de una manera que use una función asíncrona. He mostrado un ejemplo en mi respuesta: stackoverflow.com/a/49499491/2537258Usando Task, futurize y una Lista transitable, simplemente puedes hacer
Así es como configurarías esto
Otra forma de estructurar el código deseado sería
O tal vez incluso más funcionalmente orientado
Luego de la función padre
Si realmente quisiera más flexibilidad en la codificación, podría hacerlo (por diversión, estoy usando el operador Pipe Forward propuesto )
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
fuente
Actualmente, la propiedad prototipo Array.forEach no admite operaciones asíncronas, pero podemos crear nuestro propio poly-fill para satisfacer nuestras necesidades.
¡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 ...
Podríamos hacer lo mismo para algunas de las otras funciones de matriz como map ...
... y así :)
Algunas cosas a tener en cuenta:
Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>
no tendrá esta función disponiblefuente
Solo agregando a la respuesta original
fuente
Para ver cómo puede salir mal, imprima console.log al final del método.
Cosas que pueden salir mal en general:
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.
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:
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:
fuente
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.
Método 2: usar lo mismo que una función genérica de Array.prototype
Array.prototype.forEachAsync.js
Uso:
Método 3:
Usando Promise.all
Método 4: tradicional para bucle o moderno para bucle
fuente
Promise.all
deberían haberse utilizado: no tienen en cuenta ninguno de los muchos casos extremos.Promise.all
.Promise.all
no es posible peroasync
/await
es. Y no,forEach
absolutamente no maneja ningún error de promesa.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:
¿Cómo utilizar?
fuente
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
Aquí hay una manera (aún en uso
Array.prototype.forEach
) para imprimir el contenido de los archivos en paralelofuente
Similar a Antonio Val
p-iteration
, un módulo npm alternativo esasync-af
:Alternativamente,
async-af
tiene un método estático (log / logAF) que registra los resultados de las promesas:Sin embargo, la principal ventaja de la biblioteca es que puede encadenar métodos asincrónicos para hacer algo como:
async-af
fuente
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.
fuente