Verwenden von async / await mit einer forEach-Schleife

Jun 02 2016

Gibt es Probleme bei der Verwendung von async/ awaitin einer forEachSchleife? Ich versuche, ein Array von Dateien und awaitden 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/ awaitin einer Funktion höherer Ordnung wie dieser nicht verwenden sollen , deshalb wollte ich nur fragen, ob dies ein Problem darstellt.

Antworten

2680 Bergi Jun 02 2016 at 02:02

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 printFilesFunktion 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 … ofstattdessen einfach eine moderne Schleife, in der awaitwie 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 asyncAufruf der Rückruffunktion gibt ein Versprechen zurück, aber Sie werfen sie weg, anstatt auf sie zu warten. Verwenden Sie mapstattdessen 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)
  }));
}
274 FranciscoMateo Jun 15 2018 at 18:17

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

81 TimothyZorn Mar 27 2018 at 02:48

Anstelle von Promise.allin Verbindung mit Array.prototype.map(was nicht die Reihenfolge garantiert, in der die Promises 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());
}
35 AntonioVal Jul 10 2017 at 15:15

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);
  });
})();
32 Matt Mar 22 2018 at 22:11

Hier sind einige forEachAsyncPrototypen. 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).

9 chharvey Feb 23 2018 at 07:47

Zusätzlich zu @ Bergis Antwort möchte ich eine dritte Alternative anbieten. Es ist dem zweiten Beispiel von @ Bergi sehr ähnlich, aber anstatt jedes readFileeinzeln 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.readFileohnehin ein Promise-Objekt zurückgegeben wird. Daher promisesgibt 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 filesArray kommt. Bei meiner obigen Methode ist jedoch garantiert, dass die Konsole die Dateien in derselben Reihenfolge wie das bereitgestellte Array protokolliert.

7 master_dodo May 27 2019 at 05:08

Bergis Lösung funktioniert gut, wenn sie auf fsVersprechen basiert. Sie können verwendet werden bluebird, fs-extraoder fs-promisefür diese.

Die Lösung für die native fsBibliothek 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
6 HoomanAskari Aug 26 2017 at 17:47

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)))
5 JayEdwards Sep 23 2017 at 06:03

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));
}
5 OliverDixon Apr 17 2020 at 00:18

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 ...
})
4 LeOn-HanLi Sep 25 2017 at 03:00

Eine wichtige Einschränkung ist: Die await + for .. ofMethode und der forEach + asyncWeg haben tatsächlich unterschiedliche Auswirkungen.

Wenn Sie sich awaitin einer echten forSchleife befinden, wird sichergestellt, dass alle asynchronen Aufrufe einzeln ausgeführt werden. Und der forEach + asyncWeg 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/awaitund 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);
    }
}
4 gsaandy Dec 01 2019 at 23:59

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

4 lukaswilkeer Dec 21 2019 at 08:11

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

readFilesQueueliegt außerhalb der printFilesUrsache 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.

3 Babakness Feb 28 2018 at 11:41

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

3 Beau Mar 13 2019 at 06:31

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
3 PranavKAndro Nov 25 2019 at 03:31

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.

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);
3 richytong May 21 2020 at 03:57

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.forEachkeine 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)
}
2 jgmjgm Oct 15 2019 at 01:35

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

ScottRudiger Jun 21 2018 at 23:55

Ähnlich wie bei Antonio Val ist p-iterationein 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();

async-af