Y a-t-il des problèmes avec l'utilisation async
/ await
en forEach
boucle? J'essaie de parcourir un tableau de fichiers et await
le contenu de chaque fichier.
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()
Ce code fonctionne, mais quelque chose pourrait-il mal se passer avec ça? Quelqu'un m'a dit que tu n'étais pas censé utiliserasync
/ await
dans une fonction d'ordre supérieur comme celle-ci, alors je voulais juste vous demander s'il y avait un problème avec cela.
for ... of ...
fonctionne?async
/await
en fonction de générateur etforEach
cela signifie que chaque itération a une fonction de générateur individuelle, qui n'a rien à voir avec les autres. ils seront donc exécutés indépendamment et sans aucun contextenext()
avec les autres. En fait, une simplefor()
boucle fonctionne également car les itérations sont également dans une seule fonction de générateur.await
suspend l' évaluation de la fonction actuelle , y compris toutes les structures de contrôle. Oui, il est assez similaire aux générateurs à cet égard (c'est pourquoi ils sont utilisés pour polyfill asynchroniser / attendre).async
fonction est assez différente d'unPromise
rappel d'exécuteur, mais oui, lemap
rappel renvoie une promesse dans les deux cas.Avec ES2018, vous êtes en mesure de simplifier considérablement toutes les réponses ci-dessus pour:
Voir spec: proposition-async-itération
10/09/2018: Cette réponse a suscité beaucoup d'attention récemment, veuillez consulter le billet de blog d'Axel Rauschmayer pour plus d'informations sur l'itération asynchrone: ES2018: itération asynchrone
la source
of
devrait être la fonction async qui retournera un tableau. Cela ne fonctionne pas et Francisco a dit;Au lieu de
Promise.all
conjointement avecArray.prototype.map
(qui ne garantit pas l'ordre dans lequel lesPromise
s sont résolus), j'utiliseArray.prototype.reduce
, en commençant par un résoluPromise
:la source
Promise.resolve()
etawait promise;
?Promise.resolve()
renvoie unPromise
objet déjà résolu , donc quireduce
doitPromise
commencer par.await promise;
attendra que le dernierPromise
de la chaîne soit résolu. @GollyJer Les fichiers seront traités séquentiellement, un à la fois.La p-itération module sur npm implémente les méthodes d'itération de tableau afin qu'elles puissent être utilisées d'une manière très simple avec async / wait.
Un exemple avec votre cas:
la source
some
plutôt queforEach
. Merci!Voici quelques
forEachAsync
prototypes. Notez que vous en aurez besoinawait
:Notez que même si vous pouvez l'inclure dans votre propre code, vous ne devez pas l'inclure dans les bibliothèques que vous distribuez à d'autres (pour éviter de polluer leurs globaux).
la source
_forEachAsync
), c'est raisonnable. Je pense aussi que c'est la meilleure réponse car elle enregistre beaucoup de code standard.globals.js
serait bien), nous pouvons ajouter des globaux comme nous le souhaitons.En plus de la réponse de @ Bergi , j'aimerais offrir une troisième alternative. C'est très similaire au 2ème exemple de @ Bergi, mais au lieu d'attendre chacun
readFile
individuellement, vous créez un tableau de promesses, chacune que vous attendez à la fin.Notez que la fonction passée à
.map()
n'a pas besoin d'êtreasync
, carfs.readFile
retourne quand même un objet Promise. C'est doncpromises
un tableau d'objets Promise, qui peut être envoyé àPromise.all()
.Dans la réponse de @ Bergi, la console peut enregistrer le contenu des fichiers dans l'ordre de leur lecture. Par exemple, si un très petit fichier termine la lecture avant un très gros fichier, il sera enregistré en premier, même si le petit fichier vient après le gros fichier du
files
tableau. Cependant, dans ma méthode ci-dessus, vous êtes assuré que la console enregistrera les fichiers dans le même ordre que le tableau fourni.la source
await Promise.all
), mais les fichiers peuvent avoir été lus dans un ordre différent, ce qui contredit votre déclaration "vous avez la garantie que la console enregistrera les fichiers dans le même ordre qu'ils le sont" lis".La solution de Bergi fonctionne bien quand elle
fs
est basée sur des promesses. Vous pouvez utiliserbluebird
,fs-extra
oufs-promise
pour cela.Cependant, la solution pour la
fs
bibliothèque native du nœud est la suivante:Remarque:
require('fs')
prend obligatoirement la fonction comme 3e argument, sinon renvoie une erreur:la source
Les deux solutions ci-dessus fonctionnent, cependant, Antonio fait le travail avec moins de code, voici comment cela m'a aidé à résoudre les données de ma base de données, à partir de plusieurs références enfants différentes, puis à les pousser toutes dans un tableau et à les résoudre dans une promesse après tout est terminé:
la source
il est assez facile de faire apparaître quelques méthodes dans un fichier qui gérera les données asynchrones dans un ordre sérialisé et donnera une saveur plus conventionnelle à votre code. Par exemple:
maintenant, en supposant que ce soit enregistré dans './myAsync.js', vous pouvez faire quelque chose de similaire à ce qui suit dans un fichier adjacent:
la source
Comme la réponse de @ Bergi, mais avec une différence.
Promise.all
rejette toutes les promesses si l'on est rejeté.Donc, utilisez une récursivité.
PS
readFilesQueue
est en dehors de laprintFiles
cause de l'effet secondaire * introduit parconsole.log
, il est préférable de se moquer, de tester et / ou d'espionner, donc ce n'est pas cool d'avoir une fonction qui renvoie le contenu (sidenote).Par conséquent, le code peut simplement être conçu par cela: trois fonctions séparées qui sont "pures" ** et n'introduisent aucun effet secondaire, traitent la liste entière et peuvent facilement être modifiées pour gérer les cas ayant échoué.
Édition future / état actuel
Le nœud prend en charge l'attente de niveau supérieur (cela n'a pas encore de plugin, ne l'aura pas et peut être activé via les indicateurs d'harmonie), c'est cool mais ne résout pas un problème (stratégiquement, je ne travaille que sur les versions LTS). Comment obtenir les fichiers?
Utilisation de la composition. Étant donné le code, cela me donne l'impression que c'est à l'intérieur d'un module, donc, devrait avoir une fonction pour le faire. Sinon, vous devez utiliser un IIFE pour envelopper le code de rôle dans une fonction asynchrone créant un module simple qui fait tout pour vous, ou vous pouvez choisir la bonne façon, il y a, la composition.
Notez que le nom de la variable change en raison de la sémantique. Vous passez un foncteur (une fonction qui peut être invoquée par une autre fonction) et recevez un pointeur sur la mémoire qui contient le bloc logique initial de l'application.
Mais, si ce n'est pas un module et que vous devez exporter la logique?
Enveloppez les fonctions dans une fonction asynchrone.
Ou changez les noms des variables, peu importe ...
*
par effet secondaire menans tout effet colacteral de l'application qui peut changer le statut / comportement ou introuce des bogues dans l'application, comme IO.**
par "pure", c'est dans l'apostrophe car les fonctions ne sont pas pures et le code peut être convergé vers une version pure, quand il n'y a pas de sortie console, seulement des manipulations de données.De plus, pour être pur, vous devrez travailler avec des monades qui gèrent les effets secondaires, qui sont sujettes aux erreurs et traitent cette erreur séparément de l'application.
la source
Une mise en garde importante est la suivante: la
await + for .. of
méthode et laforEach + async
manière ont en fait un effet différent.Avoir à l'
await
intérieur d'une vraiefor
boucle s'assurera que tous les appels asynchrones sont exécutés un par un. Et laforEach + async
façon de tirer toutes les promesses en même temps, ce qui est plus rapide mais parfois débordé ( si vous effectuez une requête DB ou visitez certains services Web avec des restrictions de volume et que vous ne souhaitez pas transférer 100000 appels à la fois).Vous pouvez également utiliser
reduce + promise
(moins élégant) si vous ne l'utilisez pasasync/await
et que vous voulez vous assurer que les fichiers sont lus l' un après l'autre .Ou vous pouvez créer un forEachAsync pour vous aider, mais utiliser essentiellement le même pour la boucle sous-jacente.
la source
forEach
- accéder aux index au lieu de compter sur l'itérabilité - et passer l'index au rappel.Array.prototype.reduce
d'une manière qui utilise une fonction asynchrone. J'ai montré un exemple dans ma réponse: stackoverflow.com/a/49499491/2537258En utilisant Task, futurize et une liste traversable, vous pouvez simplement faire
Voici comment procéder
Une autre façon de structurer le code souhaité serait de
Ou peut-être encore plus orienté fonctionnellement
Puis à partir de la fonction parent
Si vous vouliez vraiment plus de flexibilité dans l'encodage, vous pouvez simplement le faire (pour le plaisir, j'utilise l' opérateur proposé Pipe Forward )
PS - Je n'ai pas essayé ce code sur la console, il pourrait y avoir des fautes de frappe ... "freestyle tout droit, en haut du dôme!" comme diraient les enfants des années 90. :-p
la source
Actuellement, la propriété prototype Array.forEach ne prend pas en charge les opérations asynchrones, mais nous pouvons créer notre propre remplissage poly pour répondre à nos besoins.
Et c'est tout! Vous disposez désormais d'une méthode async forEach disponible sur tous les tableaux définis après ces opérations.
Testons-le ...
Nous pourrions faire de même pour certaines des autres fonctions du tableau comme map ...
... etc :)
Quelques points à noter:
Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>
n'auront pas cette fonctionnalité disponiblela source
Ajout juste à la réponse originale
la source
Pour voir comment cela peut mal tourner, imprimez console.log à la fin de la méthode.
Choses qui peuvent mal tourner en général:
Ceux-ci ne sont pas toujours faux mais sont fréquemment dans les cas d'utilisation standard.
Généralement, l'utilisation de forEach entraînera tout sauf le dernier. Il appellera chaque fonction sans attendre la fonction, ce qui signifie qu'il indique à toutes les fonctions de démarrer puis se termine sans attendre la fin des fonctions.
C'est un exemple en JS natif qui préservera l'ordre, empêchera la fonction de revenir prématurément et en théorie conservera des performances optimales.
Cette volonté:
Avec cette solution, le premier fichier sera affiché dès qu'il sera disponible sans avoir à attendre que les autres soient disponibles en premier.
Il chargera également tous les fichiers en même temps plutôt que d'avoir à attendre la fin du premier avant de pouvoir démarrer la lecture du deuxième fichier.
Le seul inconvénient de cela et de la version d'origine est que si plusieurs lectures sont démarrées en même temps, il est plus difficile de gérer les erreurs car il y a plus d'erreurs qui peuvent se produire à la fois.
Avec les versions qui lisent un fichier à la fois, alors s'arrêtera en cas d'échec sans perdre de temps à essayer de lire d'autres fichiers. Même avec un système d'annulation élaboré, il peut être difficile d'éviter qu'il ne tombe en panne sur le premier fichier mais qu'il lise déjà la plupart des autres fichiers également.
Les performances ne sont pas toujours prévisibles. Alors que de nombreux systèmes seront plus rapides avec des lectures de fichiers parallèles, certains préféreront séquentiels. Certains sont dynamiques et peuvent évoluer sous charge, les optimisations qui offrent une latence ne donnent pas toujours un bon débit en cas de forte contention.
Il n'y a pas non plus de gestion des erreurs dans cet exemple. Si quelque chose exige qu'ils soient tous affichés avec succès ou pas du tout, cela ne le fera pas.
Une expérimentation approfondie est recommandée avec console.log à chaque étape et de fausses solutions de lecture de fichiers (délai aléatoire à la place). Bien que de nombreuses solutions semblent faire de même dans les cas simples, toutes présentent des différences subtiles qui nécessitent un examen supplémentaire.
Utilisez cette maquette pour aider à faire la différence entre les solutions:
la source
Aujourd'hui, je suis tombé sur plusieurs solutions pour cela. L'exécution des fonctions d'attente asynchrone dans la boucle forEach. En construisant le wrapper autour de nous, nous pouvons y arriver.
Des explications plus détaillées sur la façon dont cela fonctionne en interne, pour le forEach natif et pourquoi il n'est pas en mesure d'effectuer un appel de fonction asynchrone et d'autres détails sur les différentes méthodes sont fournies dans le lien ici
Les multiples façons dont cela peut être fait et ce sont les suivantes,
Méthode 1: utilisation du wrapper.
Méthode 2: utiliser la même chose qu'une fonction générique de Array.prototype
Array.prototype.forEachAsync.js
Utilisation:
Méthode 3:
Utilisation de Promise.all
Méthode 4: traditionnelle pour boucle ou moderne pour boucle
la source
Promise.all
auraient dû être utilisées - elles ne prennent en compte aucun des nombreux cas marginaux.Promise.all
.Promise.all
n'est pas possible maisasync
/await
est. Et non,forEach
ne gère absolument aucune erreur de promesse.Cette solution est également optimisée en mémoire afin que vous puissiez l'exécuter sur 10 000 éléments de données et requêtes. Certaines des autres solutions ici planteront le serveur sur des ensembles de données volumineux.
En TypeScript:
Comment utiliser?
la source
Vous pouvez utiliser
Array.prototype.forEach
, mais async / attendent n'est pas si compatible. En effet, la promesse renvoyée par un rappel asynchrone s'attend à être résolue, maisArray.prototype.forEach
ne résout aucune promesse de l'exécution de son rappel. Ainsi, vous pouvez utiliser forEach, mais vous devrez gérer vous-même la résolution de la promesse.Voici un moyen de lire et d'imprimer chaque fichier en série en utilisant
Array.prototype.forEach
Voici un moyen (toujours utilisé
Array.prototype.forEach
) d'imprimer le contenu des fichiers en parallèlela source
Semblable à Antonio Val
p-iteration
, un module npm alternatif estasync-af
:Alternativement,
async-af
a une méthode statique (log / logAF) qui enregistre les résultats des promesses:Cependant, le principal avantage de la bibliothèque est que vous pouvez chaîner des méthodes asynchrones pour faire quelque chose comme:
async-af
la source
J'utiliserais les modules pify et async bien testés (des millions de téléchargements par semaine) . Si vous n'êtes pas familier avec le module async, je vous recommande fortement de consulter ses documents . J'ai vu plusieurs développeurs perdre du temps à recréer ses méthodes, ou pire, rendre le code asynchrone difficile à maintenir lorsque les méthodes asynchrones d'ordre supérieur simplifieraient le code.
la source