Verwenden von async / await mit einer forEach-Schleife
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 habe mir von jemandem sagen lassen, dass Sie async
/ await
in einer Funktion höherer Ordnung wie dieser nicht verwenden sollen , deshalb wollte ich nur fragen, ob dies ein Problem darstellt.
Antworten
Sicher, der Code funktioniert, aber ich bin mir ziemlich sicher, dass er nicht das tut, was Sie von ihm erwarten. Es werden nur mehrere asynchrone Aufrufe ausgelöst, aber die printFiles
Funktion kehrt sofort danach zurück.
Nacheinander lesen
Wenn Sie die Dateien nacheinander lesen möchten, können Sie sie in derforEach
Tat nicht verwenden . Verwenden Sie for … of
stattdessen einfach eine moderne Schleife, in der await
wie erwartet funktioniert:
async function printFiles () {
const files = await getFilePaths();
for (const file of files) {
const contents = await fs.readFile(file, 'utf8');
console.log(contents);
}
}
Parallel lesen
Wenn Sie die Dateien parallel lesen möchten, können Sie sie in derforEach
Tat nicht verwenden . Jeder async
Aufruf der Rückruffunktion gibt ein Versprechen zurück, aber Sie werfen sie weg, anstatt auf sie zu warten. Verwenden Sie map
stattdessen einfach , und Sie können auf die Reihe von Versprechungen warten, die Sie erhalten 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)
}));
}
Mit ES2018 können Sie alle oben genannten Antworten erheblich vereinfachen:
async function printFiles () {
const files = await getFilePaths()
for await (const contents of fs.readFile(file, 'utf8')) {
console.log(contents)
}
}
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
Anstelle von Promise.all
in Verbindung mit Array.prototype.map
(was nicht die Reihenfolge garantiert, in der die Promise
s aufgelöst werden) verwende ich Array.prototype.reduce
, beginnend mit einem aufgelösten 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());
}
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:
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);
});
})();
Hier sind einige forEachAsync
Prototypen. Beachten Sie, dass Sie sie benötigen 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));
}
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 zu vermeiden, dass deren globale Daten verschmutzt werden).
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.
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);
}
Beachten Sie, dass die übergebene Funktion .map()
nicht sein muss async
, da fs.readFile
ohnehin ein Promise-Objekt zurückgegeben wird. Daher promises
gibt es ein Array von Promise-Objekten, an die gesendet werden kann Promise.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 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.
Bergis Lösung funktioniert gut, wenn sie auf fs
Versprechen basiert. Sie können verwendet werden bluebird
, fs-extra
oder fs-promise
für diese.
Die Lösung für die native fs
Bibliothek des Knotens lautet jedoch wie folgt:
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;
}));
Hinweis:
require('fs')
Zwingt die Funktion als drittes Argument, andernfalls wird ein Fehler ausgegeben:
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
Beide oben genannten Lösungen funktionieren jedoch, Antonio's erledigt die Arbeit mit weniger Code. Hier erfahren Sie, 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:
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)))
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:
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;
};
};
Angenommen, dies ist unter './myAsync.js' gespeichert, können Sie in einer benachbarten Datei etwas Ähnliches wie das Folgende tun:
...
/* 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));
}
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 führen zum Absturz des Servers bei großen Datenmengen.
In 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);
}
}
Wie benutzt man?
await asyncForEach(receipts, async (eachItem) => {
await ...
})
Eine wichtige Einschränkung ist: Die await + for .. of
Methode und der forEach + async
Weg haben tatsächlich unterschiedliche Auswirkungen.
Wenn Sie sich await
in einer echten for
Schleife befinden, wird sichergestellt, dass alle asynchronen Aufrufe einzeln ausgeführt werden. Und der forEach + 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 verwenden async/await
und sicherstellen möchten, dass Dateien nacheinander gelesen werden .
files.reduce((lastPromise, file) =>
lastPromise.then(() =>
fs.readFile(file, 'utf8')
), Promise.resolve()
)
Oder Sie können ein forEachAsync erstellen, um zu helfen, aber im Grunde dasselbe für die zugrunde liegende Schleife verwenden.
Array.prototype.forEachAsync = async function(cb){
for(let x of this){
await cb(x);
}
}
Einfach zur ursprünglichen Antwort hinzufügen
- Die Syntax des parallelen Lesens in der ursprünglichen Antwort ist manchmal verwirrend und schwer zu lesen. Vielleicht können wir sie in einem anderen Ansatz schreiben
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);
}
- Für den sequentiellen Betrieb, nicht nur für ... von , funktioniert auch die normale for-Schleife
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);
}
}
Wie @ Bergis Antwort, aber mit einem Unterschied.
Promise.all
lehnt alle Versprechen ab, wenn man abgelehnt wird.
Verwenden Sie also eine Rekursion.
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()
PS
readFilesQueue
liegt außerhalb der printFiles
Ursache der Nebenwirkung *, die durch eingeführt wird console.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.
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)
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.
// more complex version with IIFE to a single module
(async (files) => readFiles(await files())(getFilesPath)
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.
export const readFilesQueue = async () => {
// ... to code goes here
}
Oder ändern Sie die Namen von Variablen, was auch immer ...
*
Als Nebenwirkung kann jeder kolakterale Effekt der Anwendung auftreten, der 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 fehleranfälligen Nebeneffekt behandeln und diesen Fehler separat von der Anwendung behandeln.
Mit Task, Futurize und einer durchlaufbaren Liste können Sie dies einfach tun
async function printFiles() {
const files = await getFiles();
List(files).traverse( Task.of, f => readFile( f, 'utf-8'))
.fork( console.error, console.log)
}
Hier ist, wie Sie dies einrichten würden
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)
Eine andere Möglichkeit, den gewünschten Code zu strukturieren, wäre
const printFiles = files =>
List(files).traverse( Task.of, fn => readFile( fn, 'utf-8'))
.fork( console.error, console.log)
Oder vielleicht noch funktionaler ausgerichtet
// 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)
Dann von der übergeordneten Funktion
async function main() {
/* awesome code with side-effects before */
printFiles( await getFiles() );
/* awesome code with side-effects after */
}
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 ).
import { curry, flip } from 'ramda'
export const readFile = fs.readFile
|> future,
|> curry,
|> flip
export const readFileUtf8 = readFile('utf-8')
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
Derzeit unterstützt die Prototyp-Eigenschaft Array.forEach keine asynchronen Vorgänge, aber wir können unsere eigene Poly-Füllung erstellen, um unsere Anforderungen zu erfüllen.
// 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}
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 ...
// 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
Wir könnten dasselbe für einige der anderen Array-Funktionen wie map tun ...
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
... und so weiter :)
Einige Dinge zu beachten:
- Ihre iteratorFunction muss eine asynchrone Funktion oder ein Versprechen sein
- Bei zuvor erstellten Arrays ist
Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>
diese Funktion nicht verfügbar
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.
Die verschiedenen Möglichkeiten, wie dies getan werden kann, sind wie folgt:
Methode 1: Verwenden des Wrappers.
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')
}
});
});
})();
Methode 2: Verwenden derselben Funktion als generische Funktion von 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');
}
})
});
};
}
Verwendung :
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
Methode 3:
Verwenden von Promise.all
await Promise.all(items.map(async (item) => {
await someAPICall();
count++;
}));
console.log("count = " + count);
Methode 4: Traditionell für Schleife oder modern für Schleife
// 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);
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, jedoch Array.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
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
}
Hier ist eine Möglichkeit (noch verwendet Array.prototype.forEach
), den Inhalt von Dateien parallel zu drucken
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)
}
Um zu sehen, wie das schief gehen kann, drucken Sie console.log am Ende der Methode.
Dinge, die im Allgemeinen schief gehen können:
- Beliebige Reihenfolge.
- printFiles kann vor dem Drucken von Dateien ausgeführt werden.
- Schwache Leistung.
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.
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()
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:
- Initiieren Sie alle Dateilesevorgänge parallel.
- Behalten Sie die Reihenfolge bei, indem Sie mithilfe der Karte Dateinamen den Versprechungen zuordnen, auf die Sie warten müssen.
- Warten Sie auf jedes Versprechen in der vom Array festgelegten Reihenfolge.
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 Datei fertig 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 Dateilesungen schneller sind, bevorzugen einige sequentielle. Einige sind dynamisch und können sich unter Last verschieben. Optimierungen, die Latenz bieten, führen bei starken Konflikten nicht immer zu einem 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:
(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}`);
})();
Ähnlich wie bei Antonio Val ist p-iteration
ein alternatives npm-Modul 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();
Alternativ verfügt es async-af
über eine statische Methode (log / logAF), die die Ergebnisse von Versprechungen protokolliert:
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();
Der Hauptvorteil der Bibliothek besteht jedoch darin, dass Sie asynchrone Methoden verketten können, um Folgendes zu tun:
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();