297 votes

Rappel après que tous les rappels asynchrones forEach ont été effectués

Comme le titre l'indique. Comment procéder ?

Je veux appeler whenAllDone() après que la boucle forEach a parcouru chaque élément et effectué un traitement asynchrone.

[1, 2, 3].forEach(
  function(item, index, array, done) {
     asyncFunction(item, function itemDone() {
       console.log(item + " done");
       done();
     });
  }, function allDone() {
     console.log("All done");
     whenAllDone();
  }
);

Est-il possible de le faire fonctionner ainsi ? Lorsque le second argument de forEach est une fonction de rappel qui s'exécute une fois que toutes les itérations ont été parcourues ?

Résultat attendu :

3 done
1 done
2 done
All done!

515voto

Nick Tomlin Points 4258

Array.forEach n'offre pas cette possibilité (si seulement elle l'offrait), mais il existe plusieurs façons de réaliser ce que vous souhaitez :

Utilisation d'un simple compteur

function callback () { console.log('all done'); }

var itemsProcessed = 0;

[1, 2, 3].forEach((item, index, array) => {
  asyncFunction(item, () => {
    itemsProcessed++;
    if(itemsProcessed === array.length) {
      callback();
    }
  });
});

(merci à @vanuan et à d'autres) Cette approche garantit que tous les éléments sont traités avant d'invoquer le callback "done". Vous devez utiliser un compteur qui est mis à jour dans le callback. Dépendre de la valeur du paramètre index n'offre pas la même garantie, car l'ordre de retour des opérations asynchrones n'est pas garanti.

Utilisation des promesses ES6

(une bibliothèque de promesses peut être utilisée pour les navigateurs plus anciens) :

  1. Traiter toutes les demandes en garantissant une exécution synchrone (par exemple, 1 puis 2 puis 3)

    function asyncFunction (item, cb) {
      setTimeout(() => {
        console.log('done with', item);
        cb();
      }, 100);
    }
    
    let requests = [1, 2, 3].reduce((promiseChain, item) => {
        return promiseChain.then(() => new Promise((resolve) => {
          asyncFunction(item, resolve);
        }));
    }, Promise.resolve());
    
    requests.then(() => console.log('done'))
  2. Traiter toutes les demandes asynchrones sans exécution "synchrone" (2 peuvent se terminer plus vite que 1)

    let requests = [1,2,3].map((item) => {
        return new Promise((resolve) => {
          asyncFunction(item, resolve);
        });
    })
    
    Promise.all(requests).then(() => console.log('done'));

Utilisation d'une bibliothèque asynchrone

Il existe d'autres bibliothèques asynchrones, asynchrone les plus populaires, qui fournissent des mécanismes permettant d'exprimer ce que l'on veut.

Editer

Le corps de la question a été modifié pour supprimer l'exemple de code synchrone, j'ai donc mis à jour ma réponse pour la clarifier. L'exemple original utilisait un code synchrone pour modéliser un comportement asynchrone :

array.forEach es synchrone et il en est de même pour res.write Vous pouvez donc simplement placer votre callback après votre appel à foreach :

  posts.foreach(function(v, i) {
    res.write(v + ". index " + i);
  });

  res.end();

33voto

Si vous rencontrez des fonctions asynchrones et que vous voulez vous assurer qu'avant d'exécuter le code, celui-ci a terminé sa tâche, vous pouvez toujours utiliser la fonction de rappel.

Par exemple :

var ctr = 0;
posts.forEach(function(element, index, array){
    asynchronous(function(data){
         ctr++; 
         if (ctr === array.length) {
             functionAfterForEach();
         }
    })
});

Nota: functionAfterForEach est la fonction à exécuter une fois que les tâches foreach sont terminées. asynchronous est la fonction asynchrone exécutée à l'intérieur de foreach.

18voto

Adnene Belfodil Points 191

J'espère que cela résoudra votre problème, je travaille généralement avec cela lorsque j'ai besoin d'exécuter des forEach avec des tâches asynchrones à l'intérieur.

foo = [a,b,c,d];
waiting = foo.length;
foo.forEach(function(entry){
      doAsynchronousFunction(entry,finish) //call finish after each entry
}
function finish(){
      waiting--;
      if (waiting==0) {
          //do your Job intended to be done after forEach is completed
      } 
}

avec

function doAsynchronousFunction(entry,callback){
       //asynchronousjob with entry
       callback();
}

18voto

Rsh Points 939

Il est étrange de constater le nombre de réponses erronées qui ont été données aux questions suivantes asynchrone cas ! On peut simplement montrer que la vérification de l'index ne donne pas le comportement attendu :

// INCORRECT
var list = [4000, 2000];
list.forEach(function(l, index) {
    console.log(l + ' started ...');
    setTimeout(function() {
        console.log(index + ': ' + l);
    }, l);
});

salida:

4000 started
2000 started
1: 2000
0: 4000

Si nous vérifions la présence de index === array.length - 1 Le callback sera appelé à la fin de la première itération, alors que le premier élément est toujours en attente !

Pour résoudre ce problème sans utiliser de bibliothèques externes telles que async, je pense que votre meilleur choix est de sauvegarder la longueur de la liste et de décrémenter if après chaque itération. Comme il n'y a qu'un seul thread, nous sommes sûrs qu'il n'y a pas de risque de race condition.

var list = [4000, 2000];
var counter = list.length;
list.forEach(function(l, index) {
    console.log(l + ' started ...');
    setTimeout(function() {
        console.log(index + ': ' + l);
        counter -= 1;
        if ( counter === 0)
            // call your callback here
    }, l);
});

11voto

Krzysztof Grzybek Points 737

Avec ES2018, vous pouvez utiliser des itérateurs asynchrones :

const asyncFunction = a => fetch(a);
const itemDone = a => console.log(a);

async function example() {
  const arrayOfFetchPromises = [1, 2, 3].map(asyncFunction);

  for await (const item of arrayOfFetchPromises) {
    itemDone(item);
  }

  console.log('All done');
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X