Gibt es Probleme bei der Verwendung von async
/ await
in einer forEach
Schleife? Ich versuche, ein Array von Dateien und await
den Inhalt jeder Datei zu durchlaufen .
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()
Dieser Code funktioniert, aber könnte damit etwas schief gehen? Ich hatte jemanden, der mir sagte, dass Sie async
/ await
in einer Funktion höherer Ordnung wie dieser nicht verwenden sollen , also wollte ich nur fragen, ob es ein Problem damit gibt.
for ... of ...
?async
/await
in die Generatorfunktion und die VerwendungforEach
bedeutet, dass jede Iteration eine individuelle Generatorfunktion hat, die nichts mit den anderen zu tun hat. Sie werden also unabhängig ausgeführt und haben keinen Zusammenhangnext()
mit anderen. Tatsächlichfor()
funktioniert eine einfache Schleife auch, weil sich die Iterationen auch in einer einzigen Generatorfunktion befinden.await
setzt die aktuelle Funktionsbewertung einschließlich aller Kontrollstrukturen aus. Ja, diesbezüglich ist es den Generatoren ziemlich ähnlich (weshalb sie zum Polyfill-Async / Warten verwendet werden).async
Funktion unterscheidet sich stark von einemPromise
Executor-Rückruf, aber ja, dermap
Rückruf gibt in beiden Fällen ein Versprechen zurück.Mit ES2018 können Sie alle oben genannten Antworten erheblich vereinfachen, um:
Siehe Spezifikation: Vorschlag-Async-Iteration
2018-09-10: Diese Antwort hat in letzter Zeit viel Aufmerksamkeit erhalten. Weitere Informationen zur asynchronen Iteration finden Sie im Blog-Beitrag von Axel Rauschmayer: ES2018: asynchrone Iteration
quelle
of
sollte die asynchrone Funktion sein, die ein Array zurückgibt. Es funktioniert nicht und Francisco sagte;Anstelle von
Promise.all
in Verbindung mitArray.prototype.map
(was nicht die Reihenfolge garantiert, in der diePromise
s aufgelöst werden) verwende ichArray.prototype.reduce
, beginnend mit einem aufgelöstenPromise
:quelle
Promise.resolve()
und passiertawait promise;
?Promise.resolve()
gibt ein bereits aufgelöstesPromise
Objekt zurück, sodass zunächst ein Objekt erstellt werdenreduce
mussPromise
.await promise;
wartet, bis der letztePromise
in der Kette aufgelöst ist. @GollyJer Die Dateien werden nacheinander nacheinander verarbeitet.Das p-Iterationsmodul auf npm implementiert die Array-Iterationsmethoden, sodass sie mit async / await auf sehr einfache Weise verwendet werden können.
Ein Beispiel für Ihren Fall:
quelle
some
eher alsforEach
. Vielen Dank!Hier sind einige
forEachAsync
Prototypen. Beachten Sie, dass Sie sie benötigenawait
:Beachten Sie, dass Sie dies möglicherweise in Ihren eigenen Code aufnehmen, dies jedoch nicht in Bibliotheken aufnehmen sollten, die Sie an andere verteilen (um eine Verschmutzung ihrer globalen Daten zu vermeiden).
quelle
_forEachAsync
), ist dies vernünftig. Ich denke auch, dass es die schönste Antwort ist, da es viel Boilerplate-Code spart.globals.js
wäre gut), können wir Globals nach Belieben hinzufügen.Zusätzlich zu @ Bergis Antwort möchte ich eine dritte Alternative anbieten. Es ist dem zweiten Beispiel von @ Bergi sehr ähnlich, aber anstatt jedes
readFile
einzeln zu erwarten , erstellen Sie eine Reihe von Versprechungen, auf die Sie am Ende warten.Beachten Sie, dass die übergebene Funktion
.map()
nicht sein mussasync
, dafs.readFile
ohnehin ein Promise-Objekt zurückgegeben wird. Daherpromises
gibt es ein Array von Promise-Objekten, an die gesendet werden kannPromise.all()
.In der Antwort von @ Bergi protokolliert die Konsole möglicherweise den Dateiinhalt in der Reihenfolge, in der er gelesen wurde. Wenn beispielsweise eine wirklich kleine Datei vor einer wirklich großen Datei vollständig gelesen wird, wird sie zuerst protokolliert, auch wenn die kleine Datei nach der großen Datei im
files
Array kommt. Bei meiner obigen Methode ist jedoch garantiert, dass die Konsole die Dateien in derselben Reihenfolge wie das bereitgestellte Array protokolliert.quelle
await Promise.all
), aber die Dateien wurden möglicherweise in einer anderen Reihenfolge gelesen, was Ihrer Aussage widerspricht: "Sie werden garantiert, dass die Konsole die Dateien in derselben Reihenfolge protokolliert, in der sie sind." lesen".Bergis Lösung funktioniert gut, wenn sie auf
fs
Versprechen basiert. Sie können verwendet werdenbluebird
,fs-extra
oderfs-promise
für diese.Die Lösung für die native
fs
Bibliothek des Knotens lautet jedoch wie folgt:Hinweis: Die
require('fs')
Funktion wird zwangsweise als drittes Argument verwendet, andernfalls wird ein Fehler ausgegeben:quelle
Beide oben genannten Lösungen funktionieren jedoch, Antonio's erledigt die Arbeit mit weniger Code. Hier ist, wie es mir geholfen hat, Daten aus meiner Datenbank, aus mehreren verschiedenen untergeordneten Refs aufzulösen und sie dann alle in ein Array zu verschieben und sie schließlich in einem Versprechen aufzulösen erledigt:
quelle
Es ist ziemlich schmerzlos, ein paar Methoden in eine Datei einzufügen, die asynchrone Daten in einer serialisierten Reihenfolge verarbeiten und Ihrem Code eine konventionellere Note verleihen. Zum Beispiel:
Angenommen, dies ist unter './myAsync.js' gespeichert, können Sie in einer benachbarten Datei etwas Ähnliches wie das Folgende tun:
quelle
Wie @ Bergis Antwort, aber mit einem Unterschied.
Promise.all
lehnt alle Versprechen ab, wenn man abgelehnt wird.Verwenden Sie also eine Rekursion.
PS
readFilesQueue
liegt außerhalb derprintFiles
Ursache der durch * eingeführten Nebenwirkungconsole.log
. Es ist besser, sich zu verspotten, zu testen und / oder auszuspionieren. Es ist also nicht cool, eine Funktion zu haben, die den Inhalt zurückgibt (Nebenbemerkung).Daher kann der Code einfach so gestaltet werden: Drei getrennte Funktionen, die "rein" ** sind und keine Nebenwirkungen verursachen, die gesamte Liste verarbeiten und leicht geändert werden können, um fehlgeschlagene Fälle zu behandeln.
Zukünftige Bearbeitung / aktueller Status
Node unterstützt das Warten auf oberster Ebene (dies hat noch kein Plugin, wird es nicht haben und kann über Harmony-Flags aktiviert werden), es ist cool, löst aber kein Problem (strategisch arbeite ich nur an LTS-Versionen). Wie bekomme ich die Dateien?
Komposition verwenden. Angesichts des Codes entsteht für mich das Gefühl, dass sich dieses in einem Modul befindet, also sollte es eine Funktion haben, dies zu tun. Wenn nicht, sollten Sie ein IIFE verwenden, um den Rollencode in eine asynchrone Funktion zu verpacken und ein einfaches Modul zu erstellen, das alles für Sie erledigt, oder Sie können den richtigen Weg einschlagen, es gibt Komposition.
Beachten Sie, dass sich der Name der Variablen aufgrund der Semantik ändert. Sie übergeben einen Funktor (eine Funktion, die von einer anderen Funktion aufgerufen werden kann) und erhalten einen Zeiger auf den Speicher, der den anfänglichen Logikblock der Anwendung enthält.
Aber wenn es kein Modul ist und Sie die Logik exportieren müssen?
Wickeln Sie die Funktionen in eine asynchrone Funktion ein.
Oder ändern Sie die Namen von Variablen, was auch immer ...
*
Nebeneffekt Menans jede kolakterale Wirkung der Anwendung, die den Status / das Verhalten ändern oder Fehler in der Anwendung hervorrufen kann, wie z. B. IO.**
von "pure" ist es in Apostroph, da die Funktionen nicht rein sind und der Code zu einer reinen Version konvergiert werden kann, wenn keine Konsolenausgabe erfolgt, sondern nur Datenmanipulationen.Abgesehen davon müssen Sie, um rein zu sein, mit Monaden arbeiten, die den Nebeneffekt behandeln, fehleranfällig sind und diesen Fehler separat von der Anwendung behandeln.
quelle
Eine wichtige Einschränkung ist: Die
await + for .. of
Methode und derforEach + async
Weg haben tatsächlich unterschiedliche Auswirkungen.Wenn Sie sich
await
in einer echtenfor
Schleife befinden, wird sichergestellt, dass alle asynchronen Aufrufe einzeln ausgeführt werden. Und derforEach + async
Weg löst alle Versprechen gleichzeitig aus, was schneller, aber manchmal überfordert ist ( wenn Sie eine DB-Abfrage durchführen oder einige Webdienste mit Volumenbeschränkungen besuchen und nicht 100.000 Anrufe gleichzeitig auslösen möchten).Sie können auch
reduce + promise
(weniger elegant) verwenden, wenn Sie nicht verwendenasync/await
und sicherstellen möchten, dass Dateien nacheinander gelesen werden .Oder Sie können ein forEachAsync erstellen, um zu helfen, aber im Grunde dasselbe für die zugrunde liegende Schleife verwenden.
quelle
forEach
- auf Indizes zugreifen, anstatt sich auf die Iterierbarkeit zu verlassen - und den Index an den Rückruf übergeben.Array.prototype.reduce
eine asynchrone Funktion verwenden. Ich habe ein Beispiel in meiner Antwort gezeigt: stackoverflow.com/a/49499491/2537258Mit Task, Futurize und einer durchlaufbaren Liste können Sie dies einfach tun
Hier ist, wie Sie dies einrichten würden
Eine andere Möglichkeit, den gewünschten Code zu strukturieren, wäre
Oder vielleicht noch funktionaler ausgerichtet
Dann von der übergeordneten Funktion
Wenn Sie wirklich mehr Flexibilität bei der Codierung wünschen, können Sie dies einfach tun (zum Spaß verwende ich den vorgeschlagenen Pipe Forward-Operator ).
PS - Ich habe diesen Code nicht auf der Konsole ausprobiert, habe möglicherweise Tippfehler ... "Straight Freestyle, ganz oben auf der Kuppel!" wie die 90er Kinder sagen würden. :-p
quelle
Derzeit unterstützt die Prototyp-Eigenschaft Array.forEach keine asynchronen Vorgänge, aber wir können unsere eigene Polyfüllung erstellen, um unsere Anforderungen zu erfüllen.
Und das ist es! Sie haben jetzt eine asynchrone forEach-Methode für alle Arrays verfügbar, die danach für Operationen definiert werden.
Lass es uns testen ...
Wir könnten das gleiche für einige der anderen Array-Funktionen wie map tun ...
... und so weiter :)
Einige Dinge zu beachten:
Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>
diese Funktion nicht verfügbarquelle
Einfach zur ursprünglichen Antwort hinzufügen
quelle
Heute bin ich auf mehrere Lösungen dafür gestoßen. Ausführen der asynchronen Wartefunktionen in der forEach-Schleife. Indem wir den Wrapper umbauen, können wir dies erreichen.
Eine ausführlichere Erklärung, wie es intern funktioniert, für das native forEach und warum es keinen asynchronen Funktionsaufruf ausführen kann, und weitere Details zu den verschiedenen Methoden finden Sie unter dem Link hier
Die verschiedenen Möglichkeiten, wie dies getan werden kann, sind wie folgt:
Methode 1: Verwenden des Wrappers.
Methode 2: Verwenden derselben Funktion als generische Funktion von Array.prototype
Array.prototype.forEachAsync.js
Verwendungszweck :
Methode 3:
Verwenden von Promise.all
Methode 4: Traditionell für Schleife oder modern für Schleife
quelle
Promise.all
sie hätten verwendet werden sollen - sie berücksichtigen keinen der vielen Randfälle.Promise.all
.Promise.all
nicht möglich ist, aberasync
/await
ist. Und nein, behandeltforEach
absolut keine Versprechensfehler.Diese Lösung ist auch speicheroptimiert, sodass Sie sie auf 10.000 Datenelementen und Anforderungen ausführen können. Einige der anderen Lösungen hier stürzen den Server bei großen Datenmengen ab.
In TypeScript:
Wie benutzt man?
quelle
Sie können verwenden
Array.prototype.forEach
, aber async / await ist nicht so kompatibel. Dies liegt daran, dass das von einem asynchronen Rückruf zurückgegebene Versprechen voraussichtlich aufgelöst wird, jedochArray.prototype.forEach
keine Versprechen aus der Ausführung seines Rückrufs auflöst. Dann können Sie forEach verwenden, aber Sie müssen die Versprechen-Lösung selbst erledigen.Hier ist eine Möglichkeit, jede Datei in Serie mit zu lesen und zu drucken
Array.prototype.forEach
Hier ist eine Möglichkeit (noch verwendet
Array.prototype.forEach
), den Inhalt von Dateien parallel zu druckenquelle
Ähnlich wie bei Antonio Val ist
p-iteration
ein alternatives npm-Modulasync-af
:Alternativ verfügt es
async-af
über eine statische Methode (log / logAF), die die Ergebnisse von Versprechungen protokolliert:Der Hauptvorteil der Bibliothek besteht jedoch darin, dass Sie asynchrone Methoden verketten können, um Folgendes zu tun:
async-af
quelle
Um zu sehen, wie das schief gehen kann, drucken Sie console.log am Ende der Methode.
Dinge, die im Allgemeinen schief gehen können:
Diese sind nicht immer falsch, treten jedoch häufig in Standardanwendungsfällen auf.
Im Allgemeinen führt die Verwendung von forEach zu allen außer dem letzten. Es ruft jede Funktion auf, ohne auf die Funktion zu warten, was bedeutet, dass alle Funktionen gestartet und beendet werden, ohne auf das Beenden der Funktionen zu warten.
Dies ist ein Beispiel in nativem JS, das die Ordnung beibehält, verhindert, dass die Funktion vorzeitig zurückkehrt, und theoretisch die optimale Leistung beibehält.
Dieser Wille:
Bei dieser Lösung wird die erste Datei angezeigt, sobald sie verfügbar ist, ohne darauf warten zu müssen, dass die anderen zuerst verfügbar sind.
Außerdem werden alle Dateien gleichzeitig geladen, anstatt warten zu müssen, bis die erste abgeschlossen ist, bevor der zweite Lesevorgang gestartet werden kann.
Der einzige Nachteil dieser und der Originalversion besteht darin, dass es schwieriger ist, Fehler zu behandeln, wenn mehrere Lesevorgänge gleichzeitig gestartet werden, da mehr Fehler gleichzeitig auftreten können.
Bei Versionen, die jeweils eine Datei lesen, wird der Fehler dann gestoppt, ohne dass Zeit damit verschwendet wird, weitere Dateien zu lesen. Selbst mit einem ausgeklügelten Stornierungssystem kann es schwierig sein, einen Fehler bei der ersten Datei zu vermeiden, aber auch die meisten anderen Dateien bereits zu lesen.
Die Leistung ist nicht immer vorhersehbar. Während viele Systeme mit parallelen Dateilesevorgängen schneller sind, bevorzugen einige sequentielle. Einige sind dynamisch und können sich unter Last verschieben. Optimierungen, die Latenz bieten, liefern bei starken Konflikten nicht immer einen guten Durchsatz.
In diesem Beispiel gibt es auch keine Fehlerbehandlung. Wenn etwas erfordert, dass sie entweder alle erfolgreich angezeigt werden oder gar nicht, wird dies nicht der Fall sein.
Es wird empfohlen, in jeder Phase eingehende Experimente mit console.log und gefälschten Lösungen zum Lesen von Dateien durchzuführen (stattdessen zufällige Verzögerung). Obwohl viele Lösungen in einfachen Fällen dasselbe zu tun scheinen, weisen alle subtile Unterschiede auf, die eine zusätzliche Prüfung erfordern, um sie herauszuquetschen.
Verwenden Sie dieses Modell, um den Unterschied zwischen Lösungen zu erkennen:
quelle
Ich würde die gut getesteten (Millionen von Downloads pro Woche) Pify- und Async- Module verwenden. Wenn Sie mit dem Async-Modul nicht vertraut sind, empfehle ich Ihnen dringend, die Dokumentation zu lesen . Ich habe gesehen, dass mehrere Entwickler Zeit damit verschwenden, ihre Methoden neu zu erstellen, oder schlimmer noch, schwer zu wartenden asynchronen Code zu erstellen, wenn asynchrone Methoden höherer Ordnung den Code vereinfachen würden.
quelle