Utilizzo di async / await con un ciclo forEach

Jun 02 2016

Ci sono problemi con l'utilizzo di async/ awaitin un forEachciclo? Sto cercando di scorrere un array di file e awaitsul contenuto di ogni file.

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()

Questo codice funziona, ma qualcosa potrebbe andare storto? Qualcuno mi ha detto che non dovresti usare async/ awaitin una funzione di ordine superiore come questa, quindi volevo solo chiedere se c'era qualche problema con questo.

Risposte

2680 Bergi Jun 02 2016 at 02:02

Sicuramente il codice funziona, ma sono abbastanza sicuro che non fa quello che ti aspetti. Genera solo più chiamate asincrone, ma la printFilesfunzione ritorna immediatamente dopo.

Lettura in sequenza

Se vuoi leggere i file in sequenza, non puoiforEach davvero usare . Usa invece un for … ofloop moderno , in cui awaitfunzionerà come previsto:

async function printFiles () {
  const files = await getFilePaths();

  for (const file of files) {
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  }
}

Lettura in parallelo

Se vuoi leggere i file in parallelo, non puoiforEach davvero usare . Ciascuna delle asyncchiamate alla funzione di callback restituisce una promessa, ma le stai buttando via invece di aspettarle. Usa mapinvece e puoi aspettare la serie di promesse che otterrai 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

Con ES2018, sei in grado di semplificare notevolmente tutte le risposte di cui sopra a:

async function printFiles () {
  const files = await getFilePaths()

  for await (const contents of fs.readFile(file, 'utf8')) {
    console.log(contents)
  }
}

Vedi specifiche: iterazione-asincronia proposta


10/09/2018: Questa risposta ha ricevuto molta attenzione di recente, per ulteriori informazioni sull'iterazione asincrona, consultare il post sul blog di Axel Rauschmayer: ES2018: iterazione asincrona

81 TimothyZorn Mar 27 2018 at 02:48

Invece che Promise.allin combinazione con Array.prototype.map(che non garantisce l'ordine in cui Promisevengono risolti gli s), utilizzo Array.prototype.reduce, iniziando con un risolto 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

Il modulo p-iteration su npm implementa i metodi di iterazione Array in modo che possano essere usati in modo molto diretto con async / await.

Un esempio con il tuo caso:

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

Ecco alcuni forEachAsyncprototipi. Nota che avrai bisogno di awaitloro:

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

Nota anche se puoi includerlo nel tuo codice, non dovresti includerlo nelle librerie che distribuisci ad altri (per evitare di inquinare i loro globali).

9 chharvey Feb 23 2018 at 07:47

Oltre alla risposta di @ Bergi , vorrei offrire una terza alternativa. È molto simile al 2 ° esempio di @ Bergi, ma invece di aspettare readFilesingolarmente, crei una serie di promesse, ognuna che attendi alla fine.

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

Si noti che la funzione passata a .map()non deve essere necessariamente async, poiché fs.readFilerestituisce comunque un oggetto Promise. Pertanto promisesè un array di oggetti Promise, a cui è possibile inviare Promise.all().

Nella risposta di @ Bergi, la console potrebbe registrare i contenuti dei file nell'ordine in cui vengono letti. Ad esempio, se un file molto piccolo termina la lettura prima di un file molto grande, verrà registrato per primo, anche se il file piccolo viene dopo il file grande filesnell'array. Tuttavia, nel mio metodo sopra, hai la garanzia che la console registrerà i file nello stesso ordine dell'array fornito.

7 master_dodo May 27 2019 at 05:08

La soluzione di Bergi funziona bene quando fsè basata sulla promessa. È possibile utilizzare bluebird, fs-extrao fs-promiseper questo.

Tuttavia, la soluzione per la fslibreria nativa di node è la seguente:

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

Nota: require('fs') prende obbligatoriamente la funzione come terzo argomento, altrimenti genera un errore:

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
6 HoomanAskari Aug 26 2017 at 17:47

Entrambe le soluzioni di cui sopra funzionano, tuttavia, quella di Antonio fa il lavoro con meno codice, ecco come mi ha aiutato a risolvere i dati dal mio database, da diversi riferimenti figlio diversi e poi inserirli tutti in un array e risolverli in una promessa dopotutto è fatto:

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

è abbastanza indolore inserire un paio di metodi in un file che gestirà i dati asincroni in un ordine serializzato e darà un sapore più convenzionale al tuo codice. Per esempio:

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

ora, supponendo che sia stato salvato in "./myAsync.js", puoi fare qualcosa di simile al seguente in un file adiacente:

...
/* 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

Questa soluzione è anche ottimizzata per la memoria in modo da poterla eseguire su 10.000 di elementi di dati e richieste. Alcune delle altre soluzioni qui causeranno l'arresto anomalo del server su set di dati di grandi dimensioni.

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

Come usare?

await asyncForEach(receipts, async (eachItem) => {
    await ...
})
4 LeOn-HanLi Sep 25 2017 at 03:00

Un avvertimento importante è: il await + for .. ofmetodo e il forEach + asyncmodo in realtà hanno effetti diversi.

Avere awaitall'interno di un forciclo reale assicurerà che tutte le chiamate asincrone vengano eseguite una per una. E il forEach + asyncmodo in cui attiverà tutte le promesse allo stesso tempo, il che è più veloce ma a volte sopraffatto ( se esegui qualche query sul database o visiti alcuni servizi Web con limitazioni di volume e non desideri attivare 100.000 chiamate alla volta).

Puoi anche usare reduce + promise(meno elegante) se non lo usi async/awaite vuoi assicurarti che i file vengano letti uno dopo l'altro .

files.reduce((lastPromise, file) => 
 lastPromise.then(() => 
   fs.readFile(file, 'utf8')
 ), Promise.resolve()
)

Oppure puoi creare un forEachAsync per aiutare, ma fondamentalmente usa lo stesso per il ciclo sottostante.

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}
4 gsaandy Dec 01 2019 at 23:59

Aggiungendo solo alla risposta originale

  • La sintassi della lettura parallela nella risposta originale a volte è confusa e difficile da leggere, forse possiamo scriverla con un approccio diverso
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);
}

  • Per il funzionamento sequenziale, non solo per ... di , funzionerà anche il normale ciclo for
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

Come la risposta di @ Bergi, ma con una differenza.

Promise.all rifiuta tutte le promesse se una viene rifiutata.

Quindi, usa una ricorsione.

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è al di fuori della printFilescausa l'effetto collaterale * introdotto da console.log, è meglio deridere, testare e / o spiare quindi, non è bello avere una funzione che restituisca il contenuto (nota a margine).

Pertanto, il codice può essere semplicemente progettato in questo modo: tre funzioni separate che sono "pure" ** e non presentano effetti collaterali, elaborano l'intero elenco e possono essere facilmente modificate per gestire i casi falliti.

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)

Modifica futura / stato attuale

Il nodo supporta l'attesa di primo livello (questo non ha ancora un plugin, non lo avrà e può essere abilitato tramite i flag di armonia), è interessante ma non risolve un problema (strategicamente lavoro solo sulle versioni LTS). Come ottenere i file?

Usando la composizione. Dato il codice, mi fa la sensazione che questo sia all'interno di un modulo, quindi, dovrebbe avere una funzione per farlo. In caso contrario, dovresti usare un IIFE per racchiudere il codice del ruolo in una funzione asincrona creando un modulo semplice che fa tutto per te, oppure puoi andare nel modo giusto, c'è, composizione.

// more complex version with IIFE to a single module
(async (files) => readFiles(await files())(getFilesPath)

Notare che il nome della variabile cambia a causa della semantica. Si passa un funtore (una funzione che può essere invocata da un'altra funzione) e si riceve un puntatore sulla memoria che contiene il blocco di logica iniziale dell'applicazione.

Ma se non è un modulo e devi esportare la logica?

Racchiudi le funzioni in una funzione asincrona.

export const readFilesQueue = async () => {
    // ... to code goes here
}

Oppure cambia i nomi delle variabili, qualunque cosa ...


* per effetto collaterale elimina qualsiasi effetto colatterico dell'applicazione che può modificare lo stato / comportamento o introdurre bug nell'applicazione, come IO.

** da "pure", è in apostrofo poiché le funzioni non sono pure e il codice può essere convertito in una versione pura, quando non c'è output della console, solo manipolazioni di dati.

A parte questo, per essere puri, dovrai lavorare con monadi che gestiscono l'effetto collaterale, che sono inclini all'errore e tratta quell'errore separatamente dall'applicazione.

3 Babakness Feb 28 2018 at 11:41

Usando Task, futurize e un elenco attraversabile, puoi semplicemente farlo

async function printFiles() {
  const files = await getFiles();

  List(files).traverse( Task.of, f => readFile( f, 'utf-8'))
    .fork( console.error, console.log)
}

Ecco come lo configureresti

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)

Un altro modo per strutturare il codice desiderato sarebbe

const printFiles = files => 
  List(files).traverse( Task.of, fn => readFile( fn, 'utf-8'))
    .fork( console.error, console.log)

O forse ancora più orientato alla funzionalità

// 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)

Quindi dalla funzione genitore

async function main() {
  /* awesome code with side-effects before */
  printFiles( await getFiles() );
  /* awesome code with side-effects after */
}

Se davvero volevi una maggiore flessibilità nella codifica, potresti semplicemente farlo (per divertimento, sto usando l' operatore Pipe Forward proposto )

import { curry, flip } from 'ramda'

export const readFile = fs.readFile 
  |> future,
  |> curry,
  |> flip

export const readFileUtf8 = readFile('utf-8')

PS - Non ho provato questo codice sulla console, potrei avere degli errori di battitura ... "straight freestyle, off the top of the dome!" come direbbero i ragazzi degli anni '90. :-p

3 Beau Mar 13 2019 at 06:31

Attualmente la proprietà del prototipo Array.forEach non supporta le operazioni asincrone, ma possiamo creare il nostro poly-fill per soddisfare le nostre esigenze.

// 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}

E questo è tutto! Ora hai un metodo asincrono forEach disponibile su tutti gli array definiti dopo queste operazioni.

Proviamolo ...

// 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

Potremmo fare lo stesso per alcune delle altre funzioni di array come map ...

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

... e così via :)

Alcune cose da notare:

  • Il tuo iteratorFunction deve essere una funzione o una promessa asincrona
  • Tutti gli array creati in precedenza Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>non avranno questa funzione disponibile
3 PranavKAndro Nov 25 2019 at 03:31

Oggi mi sono imbattuto in più soluzioni per questo. L'esecuzione dell'asincronia attende le funzioni nel ciclo forEach. Costruendo l'involucro intorno possiamo farlo accadere.

Spiegazioni più dettagliate su come funziona internamente, per il nativo forEach e perché non è in grado di effettuare una chiamata di funzione asincrona e altri dettagli sui vari metodi sono forniti nel link qui

I molteplici modi attraverso i quali può essere fatto e sono i seguenti,

Metodo 1: utilizzo del wrapper.

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

Metodo 2: utilizzo uguale a una funzione generica di 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');
            }
        })
      });
    };
  }

Utilizzo:

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

Metodo 3:

Utilizzando Promise.all

  await Promise.all(items.map(async (item) => {
        await someAPICall();
        count++;
    }));

    console.log("count = " + count);

Metodo 4: tradizionale per loop o moderno per loop

// 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

Puoi usare Array.prototype.forEach, ma async / await non è così compatibile. Questo perché la promessa restituita da una richiamata asincrona si aspetta di essere risolta, ma Array.prototype.forEachnon risolve alcuna promessa dall'esecuzione della sua richiamata. Quindi, puoi usare forEach, ma dovrai gestire tu stesso la risoluzione della promessa.

Ecco un modo per leggere e stampare ogni file in serie utilizzando 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
}

Ecco un modo (ancora in uso Array.prototype.forEach) per stampare il contenuto dei file in parallelo

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

Per vedere come ciò può andare storto, stampa console.log alla fine del metodo.

Cose che possono andare storte in generale:

  • Ordine arbitrario.
  • printFiles può terminare l'esecuzione prima di stampare i file.
  • Scarse prestazioni.

Questi non sono sempre sbagliati, ma spesso si trovano in casi d'uso standard.

Generalmente, l'utilizzo di forEach risulterà in tutto tranne l'ultimo. Chiamerà ogni funzione senza attendere la funzione, il che significa che dice a tutte le funzioni di iniziare e poi finisce senza aspettare che le funzioni finiscano.

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()

Questo è un esempio in JS nativo che preserverà l'ordine, impedirà alla funzione di tornare prematuramente e in teoria manterrà prestazioni ottimali.

Questo sarà:

  • Avvia tutte le letture dei file in modo che avvengano in parallelo.
  • Mantieni l'ordine tramite l'uso di map per mappare i nomi dei file alle promesse di attesa.
  • Attendi ogni promessa nell'ordine definito dall'array.

Con questa soluzione verrà mostrato il primo file appena disponibile senza dover attendere prima che siano disponibili gli altri.

Inoltre caricherà tutti i file contemporaneamente invece di dover attendere il termine del primo prima di poter avviare la lettura del secondo file.

L'unico inconveniente di questo e della versione originale è che se vengono avviate più letture contemporaneamente, è più difficile gestire gli errori a causa di più errori che possono verificarsi contemporaneamente.

Con le versioni che leggono un file alla volta, allora si fermeranno in caso di errore senza perdere tempo a cercare di leggere altri file. Anche con un elaborato sistema di cancellazione può essere difficile evitare che fallisca sul primo file ma leggendo già la maggior parte degli altri file.

Le prestazioni non sono sempre prevedibili. Mentre molti sistemi saranno più veloci con letture di file parallele, alcuni preferiranno sequenziali. Alcuni sono dinamici e possono spostarsi sotto carico, le ottimizzazioni che offrono latenza non sempre producono un buon throughput in condizioni di forte contesa.

Non c'è nemmeno la gestione degli errori in quell'esempio. Se qualcosa richiede che vengano mostrati tutti con successo o non lo siano affatto, non lo farà.

Si consiglia una sperimentazione approfondita con console.log in ogni fase e soluzioni di lettura di file falsi (invece ritardo casuale). Sebbene molte soluzioni sembrino fare lo stesso in casi semplici, tutte presentano sottili differenze che richiedono un ulteriore esame per essere eliminate.

Usa questa simulazione per capire la differenza tra le soluzioni:

(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

Simile a Antonio Val's p-iteration, un modulo npm alternativo è 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();

In alternativa, async-afha un metodo statico (log / logAF) che registra i risultati delle promesse:

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

Tuttavia, il vantaggio principale della libreria è che puoi concatenare metodi asincroni per fare qualcosa come:

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