Usando async / attendi con un ciclo forEach

Ci sono problemi con l’utilizzo async/await in un ciclo forEach ? Sto provando a scorrere una serie di file e await il 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 in questo? Qualcuno mi ha detto che non dovresti usare async/await in una funzione di ordine superiore come questa, quindi volevo solo chiedere se ci fosse qualche problema con questo.

Certo, il codice funziona, ma sono abbastanza sicuro che non faccia quello che ti aspetti che faccia. Attiva solo più chiamate asincrone, ma la funzione printFiles torna immediatamente dopo.

Se si desidera leggere i file in sequenza, non è ansible utilizzare in ogni caso forEach . Usa invece un moderno for … of loop, in cui l’ await funzionerà come previsto:

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

Se si desidera leggere i file in parallelo, non è ansible utilizzare in ogni caso forEach . Ciascuna delle chiamate di funzioni di callback async restituisce una promise, ma le stai buttando via invece di aspettarle. Usa invece la map 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) })); } 

Per me usare Promise.all() con map() è un po ‘difficile da comprendere e prolisso, ma se si vuole farlo in un semplice JS è il tuo scatto migliore, immagino.

Se non ti dispiace aggiungere un modulo, ho implementato i metodi di iterazione di Array in modo che possano essere usati in modo molto semplice 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); }); } printFiles() 

p-iterazione

Con ES2018, puoi 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: https://github.com/tc39/proposal-async-iteration

Ecco alcuni esempi di prototipi asincroni:

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

Invece di Promise.all in congiunzione con Array.prototype.map (che non garantisce l’ordine in cui sono risolti i Promise ), utilizzo Array.prototype.reduce , a partire da una Promise risolta:

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

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 child refs e poi spingerli tutti in un array e risolverlo in una promise 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))) 

è piuttosto semplice inserire un paio di metodi in un file che gestirà dati asincroni in un ordine serializzato e darà un sapore più convenzionale al 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)); } 

Usando Task, futurize e una lista 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 avere strutturato il codice desiderato sarebbe

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

O forse anche più orientati dal punto di vista 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 vuoi davvero una maggiore flessibilità nella codifica, puoi 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, potrebbe avere alcuni errori di battitura … “stile libero dritto, dalla cima della cupola!” come direbbero i bambini degli anni ’90. :-p

Oltre alla risposta di @ Bergi , vorrei offrire una terza alternativa. È molto simile al secondo esempio di @ Bergi, ma invece di attendere individualmente ogni file di readFile , crei una serie di promesse, ciascuna 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); } 

Nota che la funzione passata a .map() non ha bisogno di essere async , dal momento che fs.readFile restituisce comunque un object Promise. Pertanto le promises sono una serie di oggetti Promise, che possono essere inviati a Promise.all() .

Nella risposta di @ Bergi, la console può registrare il contenuto del file fuori servizio. Ad esempio, se un file molto piccolo finisce di leggere prima di un file molto grande, verrà registrato per primo, anche se il file di piccole dimensioni viene dopo il file di grandi dimensioni nella matrice di files . Tuttavia, con il metodo sopra riportato, si garantisce che la console registrerà i file nello stesso ordine in cui vengono letti.

Un avvertimento importante è: l’ await + for .. of metodo e il metodo forEach + async hanno effetti diversi.

Dopo aver await all’interno di un ciclo reale for assicurarsi che tutte le chiamate asincrone vengano eseguite una alla volta. E il forEach + async sprigiona tutte le promesse allo stesso tempo, che è più veloce ma a volte sopraffatto ( se fai qualche query su DB o visiti alcuni servizi web con restrizioni di volume e non vuoi licenziare 100.000 chiamate alla volta).

Puoi anche usare reduce + promise (meno elegante) se non usi async/await e 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); } } 

Simile alla p-iteration di Antonio Val, 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-af ha 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

Vorrei utilizzare i moduli pify e asincronici ben collaudati (milioni di download a settimana). Se non conosci il modulo asincrono, ti consiglio vivamente di dare un’occhiata ai suoi documenti . Ho visto che più sviluppatori perdevano tempo a ricreare i propri metodi, o peggio, a creare codice asincrono difficile da gestire quando i metodi asincroni di ordine superiore semplificavano 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 ```