Utilizzo di async / await con un ciclo forEach


1133

Ci sono problemi con l'utilizzo async/ awaitin un forEachciclo? Sto cercando di scorrere in una serie di file e awaitsul contenuto di ciascun 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 con questo? Qualcuno mi ha detto che non dovresti usare async/ awaitin una funzione di ordine superiore come questa, quindi volevo solo chiederti se c'era qualche problema con questo.

Risposte:


2157

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

Lettura in sequenza

Se vuoi leggere i file in sequenza, non puoi usarliforEach davvero. 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 puoi usarliforEach davvero. Ciascuna delle asyncchiamate della funzione di callback restituisce una promessa, ma le stai gettando via invece di aspettarle. Basta usare mapinvece, e puoi attendere la serie di promesse che otterrai con 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)
  }));
}

33
Potresti spiegare perché for ... of ...funziona?
Demonbane,

84
ok, so perché ... L'uso di Babel trasformerà async/ awaitin funzione generatore e l'uso forEachsignifica che ogni iterazione ha una funzione generatore individuale, che non ha nulla a che fare con le altre. quindi saranno eseguiti in modo indipendente e non hanno alcun contesto next()con gli altri. In realtà, un semplice for()ciclo funziona anche perché le iterazioni sono anche in una singola funzione del generatore.
Demonbane,

21
@Demonbane: in breve, perché è stato progettato per funzionare :-) awaitsospende l'attuale valutazione delle funzioni , comprese tutte le strutture di controllo. Sì, è abbastanza simile ai generatori in questo senso (motivo per cui vengono utilizzati per il riempimento automatico asincrono / attendono).
Bergi,

3
@ arve0 Non proprio, una asyncfunzione è molto diversa dalla Promiserichiamata di un esecutore, ma sì la maprichiamata restituisce una promessa in entrambi i casi.
Bergi,

5
Quando vieni a conoscere le promesse di JS, usa invece mezz'ora traducendo il latino. Spero che tu sia orgoglioso @Bergi;)
Félix Gagnon-Grenier,

190

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 file of fs.readFile(file, 'utf8')) {
    console.log(contents)
  }
}

Vedi le specifiche: proposta-asincrono-iterazione


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


4
Eseguito l'upgrade, sarebbe fantastico se tu potessi inserire un link alla specifica nella tua risposta per chiunque voglia saperne di più sull'iterazione asincrona.
Saadq,

8
Non dovrebbe essere contenuto anziché file
nell'iteratore

10
Perché le persone votano questa risposta? Dai un'occhiata più da vicino alla risposta, alla domanda e alla proposta. Dopo ofdovrebbe essere la funzione asincrona che restituirà un array. Non funziona e Francisco ha detto;
Yevhenii Herasymchuk

3
Totalmente d'accordo con @AntonioVal. Non è una risposta
Yevhenii Herasymchuk

2
Anche se concordo sul fatto che non è una risposta, l'upgrade di una proposta è un modo per aumentarne la popolarità rendendola potenzialmente disponibile prima da utilizzare in seguito.
Robert Molina,

62

Invece di Promise.allin congiunzione con Array.prototype.map(che non garantisce l'ordine in cui Promisevengono risolti i messaggi di posta elettronica), io uso Array.prototype.reduce, a partire da 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());
}

1
Funziona perfettamente, grazie mille. Potresti spiegare cosa sta succedendo qui con Promise.resolve()e await promise;?
parrker9

1
Questo è abbastanza bello. Ho ragione nel pensare che i file verranno letti in ordine e non tutti in una volta?
GollyJer,

1
@ parrker9 Promise.resolve()restituisce un Promiseoggetto già risolto , quindi reducedeve Promiseiniziare con. await promise;aspetteranno che l'ultimo Promisenella catena si risolva. @GollyJer I file verranno elaborati in sequenza, uno alla volta.
Timothy Zorn,

Ottimo uso di ridurre, grazie per il commento! Dico semplicemente che, a differenza di alcuni degli altri metodi menzionati nei commenti, questo è sincrono, il che significa che i file vengono letti uno dopo l'altro e non in parallelo (poiché la successiva iterazione della funzione di riduzione si basa sul precedente iterazione, deve essere sincrono).
Shay Yzhakov,

1
@Shay, vuoi dire sequenziale, non sincrono. Questo è ancora asincrono: se sono programmate altre cose, verranno eseguite tra le iterazioni qui.
Timothy Zorn,

33

Il modulo p-iteration su npm implementa i metodi di iterazione di array in modo che possano essere utilizzati in modo molto semplice con async / waitit.

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

1
Mi piace in quanto ha le stesse funzioni / metodi di JS stesso - nel mio caso avevo bisogno somepiuttosto che forEach. Grazie!
mikemaccana,

26

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 che puoi includere questo nel tuo codice, ma non dovresti includerlo nelle librerie che distribuisci ad altri (per evitare di inquinare i loro globali).


1
Anche se esiterei ad aggiungere le cose direttamente al prototipo, questa è una buona idea per ogni implementazione
DaniOcean

2
Finché il nome sarà univoco in futuro (come userei _forEachAsync) questo è ragionevole. Penso anche che sia la risposta più bella in quanto consente di risparmiare un sacco di codice boilerplate.
mikemaccana,

1
@estus Questo per evitare di inquinare il codice di altre persone. Se il codice appartiene alla nostra organizzazione personale e i globi si trovano in un file ben identificato ( globals.jssarebbe buono) possiamo aggiungere i globi a nostro piacimento.
mikemaccana,

1
@mikemaccana Questo per evitare cattive pratiche generalmente accettate. È vero, questo può essere fatto fintanto che usi solo codice proprietario, cosa che accade raramente. Il problema è che quando usi le librerie di terze parti, può esserci un altro ragazzo che si sente allo stesso modo e modifica gli stessi globi, solo perché sembrava una buona idea al momento in cui è stata scritta una libreria.
Estus Flask

1
@estus Certo. Ho aggiunto un avvertimento alla domanda per salvare la discussione (non particolarmente produttiva) qui.
mikemaccana,

7

Oltre alla risposta di @ Bergi , vorrei offrire una terza alternativa. È molto simile al secondo esempio di @ Bergi, ma invece di aspettare ciascuno readFileindividualmente, crei una serie di promesse, ognuna delle quali 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);
}

.map()Non è necessario che la funzione passata a async, poiché fs.readFilerestituisce comunque un oggetto Promise. Pertanto promisesè una matrice di oggetti Promise, a cui è possibile inviare Promise.all().

Nella risposta di @ Bergi, la console può registrare il contenuto del file nell'ordine in cui viene letto. Ad esempio, se un file di dimensioni molto piccole termina la lettura prima di un file di dimensioni molto grandi, verrà registrato per primo, anche se il file di piccole dimensioni viene dopo il file di grandi dimensioni filesnell'array. Tuttavia, nel mio metodo sopra, sei sicuro che la console registrerà i file nello stesso ordine dell'array fornito.


1
Sono abbastanza sicuro che tu non sia corretto: sono abbastanza sicuro che anche il tuo metodo possa leggere i file fuori servizio. Sì, registrerà l'output nell'ordine corretto (a causa di await Promise.all), ma i file potrebbero essere stati letti in un ordine diverso, il che contraddice la tua affermazione "sei sicuro che la console registrerà i file nello stesso ordine in cui sono leggere".
Venryx,

1
@Venryx Hai ragione, grazie per la correzione. Ho aggiornato la mia risposta.
Chharvey,

6

La soluzione di Bergi funziona bene quando fsè basata sulla promessa. Puoi usare bluebird, fs-extrao fs-promiseper questo.

Tuttavia, la soluzione per il fslibary nativo del nodo è 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') assume obbligatoriamente la funzione come terzo argomento, altrimenti genera errore:

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function

5

Entrambe le soluzioni sopra funzionano, tuttavia, Antonio fa il lavoro con meno codice, ecco come mi ha aiutato a risolvere i dati dal mio database, da diversi riferimenti secondari e quindi trasferirli tutti in un array e risolverli in una promessa dopo tutto è 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)))

4

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

2
Piccolo addendum, non dimenticare di avvolgere i tuoi wait / asyncs in blocchi try / catch !!
Jay Edwards,

4

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

Promise.all rifiuta tutte le promesse se uno viene rifiutato.

Quindi, utilizzare 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 dell'effetto collaterale * introdotto da console.log, è meglio deridere, testare o spiare, quindi non è bello avere una funzione che ritorni il contenuto (sidenote).

Pertanto, il codice può essere semplicemente progettato in base a ciò: tre funzioni separate che sono "pure" ** e non introducono effetti collaterali, elaborano l'intero elenco e possono essere facilmente modificate per gestire casi non riusciti.

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 alto livello (questo non ha ancora un plugin, non lo avrà e può essere abilitato tramite flag di armonia), è bello ma non risolve un problema (strategicamente lavoro solo su versioni LTS). Come ottenere i file?

Usando la composizione. Dato il codice, mi fa sentire che questo è all'interno di un modulo, quindi dovrebbe avere una funzione per farlo. Altrimenti, dovresti usare un IIFE per avvolgere 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)

Si noti che il nome della variabile cambia a causa della semantica. Si passa un functor (una funzione che può essere invocata da un'altra funzione) e riceve un puntatore in memoria che contiene il blocco logico iniziale dell'applicazione.

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

Avvolgi le funzioni in una funzione asincrona.

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

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


* per effetto collaterale menana qualsiasi effetto colecale dell'applicazione che può cambiare lo statuto / comportamento o introdurre bug nell'applicazione, come IO.

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

A parte questo, per essere puri, dovrai lavorare con monadi che gestiscono l'effetto collaterale, che sono soggetti a errori e trattano quell'errore separatamente dall'applicazione.


3

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

Avere awaitall'interno di un vero forciclo farà in modo che tutte le chiamate asincrone vengano eseguite una ad una. E il forEach + asyncmodo in cui verranno attivate tutte le promesse allo stesso tempo, che è più veloce ma a volte sopraffatto ( se si esegue una query DB o si visitano alcuni servizi Web con restrizioni di volume e non si desidera eseguire 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 usare lo stesso per il ciclo sottostante.

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}

Dai un'occhiata a Come definire il metodo in JavaScript su Array.prototype e Object.prototype in modo che non appaia in in loop . Inoltre, probabilmente dovresti usare la stessa iterazione nativa forEach- accedendo agli indici invece di fare affidamento sull'iterabilità - e passare l'indice al callback.
Bergi,

È possibile utilizzare Array.prototype.reducein un modo che utilizza una funzione asincrona. Ho mostrato un esempio nella mia risposta: stackoverflow.com/a/49499491/2537258
Timothy Zorn,

3

Utilizzando 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 avresti impostato

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ù funzionale

// 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 desideri davvero 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 ... "stile libero dritto, fuori dalla cima della cupola!" come direbbero i bambini degli anni '90. :-p


3

Attualmente la proprietà del prototipo Array.forEach non supporta le operazioni asincrone, ma possiamo creare il nostro polilucido 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 è disponibile un metodo asincrono per Ogni array disponibile su qualsiasi array definito dopo questo alle operazioni.

Proviamo ...

// 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 dell'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:

  • La tua iteratorFunction deve essere una funzione o una promessa asincrona
  • Qualsiasi array creato prima Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>non avrà questa funzione disponibile

3

Basta aggiungere 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 operazioni sequenziali, non solo per ... of , funzionerà anche normale per loop
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);
  }
}

2

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

Cose che possono andare male in generale:

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

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

In genere, l'utilizzo di forEach comporterà tutti tranne l'ultimo. Chiamerà ogni funzione senza attendere la funzione, il che significa che dice a tutte le funzioni di iniziare, quindi termina senza attendere il completamento delle funzioni.

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à:

  • Iniziare che tutte le letture del file avvengano in parallelo.
  • Conserva l'ordine tramite l'uso della mappa per mappare i nomi dei file alle promesse da attendere.
  • Attendi ogni promessa nell'ordine definito dall'array.

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

Inoltre, caricherà tutti i file contemporaneamente anziché dover attendere che il primo termini prima che possa essere avviato il secondo file letto.

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 accadere alla volta.

Con le versioni che leggono un file alla volta, si interromperanno in caso di errore senza perdere tempo nel tentativo di leggere altri file. Anche con un elaborato sistema di cancellazione può essere difficile evitarlo sul primo file ma leggendo anche 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 rendimento in contese pesanti.

In questo esempio non è inoltre presente la gestione degli errori. Se qualcosa richiede che tutti vengano mostrati con successo o per niente, non lo farà.

Si consiglia una sperimentazione approfondita con console.log in ogni fase e soluzioni di lettura di file falsi (ritardo casuale invece). Anche se molte soluzioni sembrano fare la stessa cosa in casi semplici, tutte presentano sottili differenze che richiedono un po 'più di scrupolo.

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

2

Oggi ho trovato diverse soluzioni per questo. L'esecuzione dell'asincrono attende le funzioni in forEach Loop. Costruendo il wrapper in giro possiamo farlo accadere.

Spiegazione più dettagliata su come funziona internamente, per il nativo per ogni 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 in cui 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 come 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);

I tuoi metodi 1 e 2 sono semplicemente implementazioni errate in cui Promise.allavrebbero dovuto essere utilizzati - non tengono conto di nessuno dei molti casi limite.
Bergi,

@Bergi: Grazie per i commenti validi, mi spiego per favore perché i metodi 1 e 2 non sono corretti. Serve anche allo scopo. Funziona molto bene Questo per dire che tutti questi metodi sono possibili, in base alla situazione in cui si può decidere di sceglierne uno. Ho l'esempio corrente per lo stesso.
PranavKAndro,

Non riesce su array vuoti, non ha alcun errore nella gestione e probabilmente più problemi. Non reinventare la ruota. Basta usare Promise.all.
Bergi,

In determinate condizioni dove non è possibile, sarà utile. Anche la gestione degli errori viene eseguita da ogni API per impostazione predefinita, quindi nessun problema. È curato!
PranavKAndro,

No, non ci sono condizioni dove Promise.allnon è possibile ma async/ awaitè. E no, forEachassolutamente non gestisce alcun errore di promessa.
Bergi,

2

Questa soluzione è anche ottimizzata per la memoria, quindi è possibile eseguirla su 10.000 di dati e richieste. Alcune delle altre soluzioni qui causeranno l'arresto anomalo del server su grandi set di dati.

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

2

Puoi usarlo Array.prototype.forEach, ma async / await non è così compatibile. Questo perché la promessa restituita da un callback asincrono prevede di essere risolta, maArray.prototype.forEach non risolve alcuna promessa dall'esecuzione del callback. Quindi, puoi usare per Ognuno, ma dovrai gestire tu stesso la risoluzione della promessa.

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

Il primo senario è ideale per i loop che devono essere eseguiti in serie e non è possibile utilizzarli per
Mark Odey,

0

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 del tipo:

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


-3

Vorrei utilizzare i moduli pify e asincroni ben collaudati (milioni di download alla settimana) . Se non hai familiarità con il modulo asincrono, ti consiglio vivamente di consultare i suoi documenti . Ho visto più sviluppatori perdere tempo a ricreare i suoi metodi, o peggio, rendendo il codice asincrono di difficile manutenzione quando i metodi asincroni di ordine superiore avrebbero semplificato il codice.

const async = require('async')
const fs = require('fs-promise')
const pify = require('pify')

async function getFilePaths() {
    return Promise.resolve([
        './package.json',
        './package-lock.json',
    ]);
}

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

  await pify(async.eachSeries)(files, async (file) => {  // <-- run in series
  // await pify(async.each)(files, async (file) => {  // <-- run in parallel
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  })
  console.log('HAMBONE')
}

printFiles().then(() => {
    console.log('HAMBUNNY')
})
// ORDER OF LOGS:
// package.json contents
// package-lock.json contents
// HAMBONE
// HAMBUNNY
```


Questo è un passo nella direzione sbagliata. Ecco una guida alla mappatura che ho creato per aiutare le persone a rimanere bloccate nell'inferno della callback nell'era moderna di JS: github.com/jmjpro/async-package-to-async-await/blob/master/… .
jbustamovej,

come potete vedere qui , sono interessato e aperto all'uso di async / waitit invece della lib asincrona. In questo momento, penso che ognuno abbia un tempo e un luogo. Non sono convinto che l'asincrono lib == "callback hell" e async / await == "l'era JS moderna". imo, quando async lib> async / wait: 1. flusso complesso (ad es. coda, carico, persino auto quando le cose si complicano) 2. concorrenza 3. array di supporto / oggetti / iterabili 4. gestione degli errori
Zachary Ryan Smith
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.