Utiliser async / await avec une boucle forEach

Jun 02 2016

Y a-t-il des problèmes avec l'utilisation de async/ awaitdans une forEachboucle? J'essaye de parcourir un tableau de fichiers et awaitsur le contenu de chaque fichier.

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

Ce code fonctionne, mais y a-t-il un problème avec cela? Quelqu'un m'a dit que vous n'êtes pas censé utiliser async/ awaitdans une fonction d'ordre supérieur comme celle-ci, donc je voulais juste demander s'il y avait un problème avec cela.

Réponses

2680 Bergi Jun 02 2016 at 02:02

Bien sûr, le code fonctionne, mais je suis à peu près sûr qu'il ne fait pas ce que vous attendez de lui. Il déclenche simplement plusieurs appels asynchrones, mais la printFilesfonction revient immédiatement après cela.

Lecture en séquence

Si vous souhaitez lire les fichiers dans l'ordre, vous ne pouvez pas enforEach effet utiliser . Utilisez simplement une for … ofboucle moderne à la place, dans laquelle awaitfonctionnera comme prévu:

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

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

Lecture en parallèle

Si vous souhaitez lire les fichiers en parallèle, vous ne pouvez enforEach effet pas utiliser . Chacun des asyncappels de fonction de rappel renvoie une promesse, mais vous les jetez au lieu de les attendre. Utilisez simplement à la mapplace, et vous pouvez attendre le tableau de promesses que vous obtiendrez 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

Avec ES2018, vous êtes en mesure de simplifier considérablement toutes les réponses ci-dessus pour:

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

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

Voir la spécification: proposition-async-itération


2018-09-10: Cette réponse a suscité beaucoup d'attention récemment, veuillez consulter l'article de blog d'Axel Rauschmayer pour plus d'informations sur l'itération asynchrone: ES2018: itération asynchrone

81 TimothyZorn Mar 27 2018 at 02:48

Au lieu de Promise.allconjointement avec Array.prototype.map(qui ne garantit pas l'ordre dans lequel les Promises sont résolus), j'utilise Array.prototype.reduce, en commençant par un résolu 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

Le module p-iteration sur npm implémente les méthodes d'itération Array afin qu'elles puissent être utilisées de manière très simple avec async / await.

Un exemple avec votre cas:

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

Voici quelques forEachAsyncprototypes. Notez que vous en aurez besoin 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));
}

Notez que bien que vous puissiez l'inclure dans votre propre code, vous ne devez pas l'inclure dans les bibliothèques que vous distribuez à d'autres (pour éviter de polluer leurs globaux).

9 chharvey Feb 23 2018 at 07:47

En plus de la réponse de @ Bergi , j'aimerais proposer une troisième alternative. C'est très similaire au deuxième exemple de @ Bergi, mais au lieu d'attendre chacune readFileindividuellement, vous créez un tableau de promesses, chacune que vous attendez à la fin.

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

Notez que la fonction passée à .map()n'a pas besoin de l'être async, car fs.readFilerenvoie de toute façon un objet Promise. Par conséquent, promisesun tableau d'objets Promise, qui peuvent être envoyés à Promise.all().

Dans la réponse de @ Bergi, la console peut enregistrer le contenu des fichiers dans l'ordre dans lequel ils sont lus. Par exemple, si un très petit fichier finit de lire avant un très gros fichier, il sera enregistré en premier, même si le petit fichier vient après le gros fichier du filestableau. Cependant, dans ma méthode ci-dessus, vous êtes assuré que la console enregistrera les fichiers dans le même ordre que le tableau fourni.

7 master_dodo May 27 2019 at 05:08

La solution de Bergi fonctionne bien lorsqu'elle fsest basée sur la promesse. Vous pouvez utiliser bluebird, fs-extraou fs-promisepour cela.

Cependant, la solution pour la fsbibliothèque native de node est la suivante:

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

Remarque: require('fs') prend obligatoirement la fonction comme troisième argument, sinon génère une erreur:

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

Les deux solutions ci-dessus fonctionnent, cependant, Antonio fait le travail avec moins de code, voici comment cela m'a aidé à résoudre les données de ma base de données, de plusieurs refs enfants différents, puis à les pousser tous dans un tableau et à les résoudre dans une promesse après tout est terminé:

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

il est assez facile d'insérer quelques méthodes dans un fichier qui gérera les données asynchrones dans un ordre sérialisé et donnera une saveur plus conventionnelle à votre code. Par exemple:

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

maintenant, en supposant qu'il soit enregistré dans './myAsync.js', vous pouvez faire quelque chose de similaire à ce qui suit dans un fichier adjacent:

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

Cette solution est également optimisée en mémoire afin que vous puissiez l'exécuter sur 10 000 éléments de données et requêtes. Certaines des autres solutions ici planteront le serveur sur de grands ensembles de données.

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

Comment utiliser?

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

Une mise en garde importante est la suivante: la await + for .. ofméthode et la forEach + asyncmanière ont en fait un effet différent.

Avoir à l' awaitintérieur d'une forboucle réelle garantira que tous les appels asynchrones sont exécutés un par un. Et le forEach + asyncchemin déclenchera toutes les promesses en même temps, ce qui est plus rapide mais parfois débordé ( si vous effectuez une requête DB ou visitez certains services Web avec des restrictions de volume et ne voulez pas lancer 100000 appels à la fois).

Vous pouvez également utiliser reduce + promise(moins élégant) si vous ne l'utilisez pas async/awaitet souhaitez vous assurer que les fichiers sont lus les uns après les autres .

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

Ou vous pouvez créer un forEachAsync pour aider, mais utiliser essentiellement la même boucle for sous-jacente.

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

Juste ajouter à la réponse originale

  • La syntaxe de lecture parallèle dans la réponse originale est parfois déroutante et difficile à lire, peut-être pouvons-nous l'écrire dans une approche différente
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);
}

  • Pour un fonctionnement séquentiel, pas seulement pour ... of , la boucle for normale fonctionnera également
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

Comme la réponse de @ Bergi, mais avec une différence.

Promise.all rejette toutes les promesses si on est rejeté.

Alors, utilisez une récursivité.

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

readFilesQueueest en dehors de la printFilescause de l'effet secondaire * introduit par console.log, il vaut mieux se moquer, tester et / ou espionner donc, ce n'est pas cool d'avoir une fonction qui renvoie le contenu (note latérale).

Par conséquent, le code peut simplement être conçu par cela: trois fonctions séparées qui sont «pures» ** et n'introduisent aucun effet secondaire, traitent la liste entière et peuvent facilement être modifiées pour gérer les cas d'échec.

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)

Modification future / état actuel

Node prend en charge l'attente de niveau supérieur (cela n'a pas encore de plugin, ne l'aura pas et peut être activé via des drapeaux d'harmonie), c'est cool mais ne résout pas un problème (stratégiquement, je ne travaille que sur les versions LTS). Comment récupérer les fichiers?

Utilisation de la composition. Compte tenu du code, cela me donne l'impression que c'est à l'intérieur d'un module, donc, devrait avoir une fonction pour le faire. Sinon, vous devriez utiliser un IIFE pour envelopper le code de rôle dans une fonction asynchrone créant un module simple qui fait tout pour vous, ou vous pouvez aller dans le bon sens, il y a la composition.

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

Notez que le nom de la variable change en raison de la sémantique. Vous passez un foncteur (une fonction qui peut être invoquée par une autre fonction) et reçoit un pointeur sur la mémoire qui contient le bloc initial de logique de l'application.

Mais si ce n'est pas un module et que vous devez exporter la logique?

Enveloppez les fonctions dans une fonction asynchrone.

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

Ou changez les noms des variables, peu importe ...


* par effet secondaire menans tout effet colactérien de l'application qui peut changer l'état / le comportement ou introduire des bogues dans l'application, comme IO.

** par "pur", c'est en apostrophe puisque les fonctions ne sont pas pures et le code peut être convergé vers une version pure, quand il n'y a pas de sortie console, seulement des manipulations de données.

En plus de cela, pour être pur, vous devrez travailler avec des monades qui gèrent l'effet secondaire, qui sont sujettes aux erreurs, et traitent cette erreur séparément de l'application.

3 Babakness Feb 28 2018 at 11:41

En utilisant Task, futurize et une liste traversable, vous pouvez simplement faire

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

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

Voici comment vous configurez cela

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)

Une autre façon d'avoir structuré le code souhaité serait

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

Ou peut-être même plus orienté fonctionnellement

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

Puis à partir de la fonction parent

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

Si vous voulez vraiment plus de flexibilité dans l'encodage, vous pouvez simplement le faire (pour le plaisir, j'utilise l' opérateur Pipe Forward proposé )

import { curry, flip } from 'ramda'

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

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

PS - Je n'ai pas essayé ce code sur la console, j'ai peut-être des fautes de frappe ... "Straight freestyle, off the top of the dome!" comme diraient les enfants des années 90. :-p

3 Beau Mar 13 2019 at 06:31

Actuellement, la propriété prototype Array.forEach ne prend pas en charge les opérations asynchrones, mais nous pouvons créer notre propre poly-fill pour répondre à nos besoins.

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

Et c'est tout! Vous disposez maintenant d'une méthode async forEach disponible sur tous les tableaux définis après ces opérations.

Testons-le ...

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

Nous pourrions faire de même pour certaines des autres fonctions de tableau comme 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

... etc :)

Quelques points à noter:

  • Votre iteratorFunction doit être une fonction asynchrone ou une promesse
  • Toutes les baies créées auparavant Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>ne disposeront pas de cette fonctionnalité
3 PranavKAndro Nov 25 2019 at 03:31

Aujourd'hui, je suis tombé sur plusieurs solutions pour cela. Exécution des fonctions d'attente asynchrone dans la boucle forEach. En construisant le wrapper autour, nous pouvons y arriver.

Des explications plus détaillées sur son fonctionnement en interne, pour le forEach natif et pourquoi il n'est pas capable de faire un appel de fonction asynchrone et d'autres détails sur les différentes méthodes sont fournis en lien ici

Les multiples moyens par lesquels cela peut être fait et ils sont les suivants,

Méthode 1: Utilisation du 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')
           }
         });
     });
    })();

Méthode 2: Utilisation de la même fonction qu'une fonction générique de 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');
            }
        })
      });
    };
  }

Utilisation:

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

Méthode 3:

Utilisation de Promise.all

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

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

Méthode 4: boucle for traditionnelle ou boucle for moderne

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

Vous pouvez utiliser Array.prototype.forEach, mais async / await n'est pas si compatible. Cela est dû au fait que la promesse retournée par un rappel asynchrone s'attend à être résolue, mais Array.prototype.forEachne résout aucune promesse de l'exécution de son rappel. Ainsi, vous pouvez utiliser forEach, mais vous devrez gérer vous-même la résolution de la promesse.

Voici un moyen de lire et d'imprimer chaque fichier en série en utilisant 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
}

Voici un moyen (toujours utilisé Array.prototype.forEach) d'imprimer le contenu des fichiers en parallèle

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

Pour voir comment cela peut mal tourner, imprimez console.log à la fin de la méthode.

Choses qui peuvent mal tourner en général:

  • Ordre arbitraire.
  • printFiles peut terminer son exécution avant l'impression des fichiers.
  • Mauvaise performance.

Celles-ci ne sont pas toujours erronées mais sont fréquemment utilisées dans des cas d'utilisation standard.

En règle générale, l'utilisation de forEach entraînera tous les fichiers sauf le dernier. Il appellera chaque fonction sans attendre la fonction, ce qui signifie qu'il dit à toutes les fonctions de démarrer puis se termine sans attendre la fin des fonctions.

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

C'est un exemple en JS natif qui préservera l'ordre, empêchera la fonction de revenir prématurément et en théorie conservera des performances optimales.

Cette volonté:

  • Lancez toutes les lectures de fichiers en parallèle.
  • Préservez l'ordre via l'utilisation de la carte pour mapper les noms de fichiers aux promesses à attendre.
  • Attendez chaque promesse dans l'ordre défini par le tableau.

Avec cette solution, le premier fichier sera affiché dès qu'il sera disponible sans avoir à attendre que les autres soient disponibles au préalable.

Il chargera également tous les fichiers en même temps plutôt que d'avoir à attendre que le premier se termine avant que la deuxième lecture de fichier puisse être lancée.

Le seul inconvénient de ceci et de la version originale est que si plusieurs lectures sont lancées à la fois, il est plus difficile de gérer les erreurs en raison du plus grand nombre d'erreurs pouvant survenir à la fois.

Avec des versions qui lisent un fichier à la fois, alors s'arrêtera en cas d'échec sans perdre de temps à essayer de lire d'autres fichiers. Même avec un système d'annulation élaboré, il peut être difficile d'éviter qu'il échoue sur le premier fichier mais en lisant déjà la plupart des autres fichiers.

Les performances ne sont pas toujours prévisibles. Alors que de nombreux systèmes seront plus rapides avec des lectures de fichiers parallèles, certains préféreront séquentiels. Certains sont dynamiques et peuvent se déplacer sous charge, les optimisations qui offrent une latence ne donnent pas toujours un bon débit en cas de forte contention.

Il n'y a pas non plus de gestion des erreurs dans cet exemple. Si quelque chose exige qu'ils soient tous affichés avec succès ou pas du tout, cela ne le fera pas.

Une expérimentation approfondie est recommandée avec console.log à chaque étape et de fausses solutions de lecture de fichiers (délai aléatoire à la place). Bien que de nombreuses solutions semblent faire la même chose dans des cas simples, toutes présentent des différences subtiles qui nécessitent un examen plus approfondi pour être évitées.

Utilisez cette simulation pour faire la différence entre les solutions:

(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

Semblable à celui d'Antonio Val p-iteration, un module npm alternatif est 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();

Alternativement, async-afa une méthode statique (log / logAF) qui enregistre les résultats des promesses:

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

Cependant, le principal avantage de la bibliothèque est que vous pouvez enchaîner des méthodes asynchrones pour faire quelque chose comme:

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