Callback une fois que toutes les callbacks asynchrones forEach sont terminés

comme son titre l'indique. Comment dois-je faire?

je veux appeler whenAllDone() après que la boucle forEach ait traversé chaque élément et ait fait 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();
  }
);

Possible de le faire fonctionner comme ça? Quand le deuxième argument à forEach est une fonction de rappel qui s'exécute une fois qu'elle a traversé toutes les itérations?

résultats escomptés:

3 done
1 done
2 done
All done!
168
demandé sur Alexis Wilke 2013-09-24 17:34:32

13 réponses

Array.forEach ne fournit pas cette gentillesse (oh si elle le ferait) mais il y a plusieurs façons d'accomplir ce que vous voulez:

utilisant un compteur simple

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 autres) cette approche garantit que tous les articles sont traités avant d'invoquer le rappel" fait". L'approche suggérée par Emil, bien qu'efficace d'après mon expérience, n'offre pas la même garantie.

utilisant ES6 Promesses

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

  1. traiter toutes les demandes garantissant une exécution synchrone (par ex. 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 "synchrone" exécution (2 peut 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'));
    

à l'Aide d'un async bibliothèque

il y a d'autres bibliothèques asynchrones, async étant les plus populaires, qui fournissent des mécanismes pour exprimer ce que vous voulez.

Modifier

le corps de la question a été modifié pour supprimer le code d'exemple précédemment synchrone, donc j'ai mis à jour ma réponse pour clarifier. L'exemple original utilisait un code semblable à synchrone pour modéliser le comportement asynchrone, de sorte que la suivante s'appliquait:

array.forEach est synchrone et ainsi est res.write , de sorte que vous pouvez simplement mettre votre rappel après votre appel à foreach:

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

  res.end();
292
répondu Nick Tomlin 2017-05-23 12:18:21

si vous rencontrez des fonctions asynchrones, et que vous voulez vous assurer qu'avant d'exécuter le code il termine sa tâche, nous pouvons toujours utiliser la capacité de rappel.

par exemple:

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

remarque: functionAfterForEach est la fonction qui doit être exécutée après le foreach les tâches sont terminées. asynchrone est la fonction asynchrone exécutée à l'intérieur de l'avant-bras.

espérons que cette aide.

21
répondu Emil Reña Enriquez 2017-03-10 01:49:27

c'est étrange combien de réponses incorrectes a été donné à asynchrone cas! On peut simplement montrer que la vérification de l'index ne fournit 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);
});

sortie:

4000 started
2000 started
1: 2000
0: 4000

si nous vérifions index === array.length - 1 , le rappel 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 comme async, je pense que votre meilleur pari est de sauver la longueur de la liste et décrément si après chaque itération. Puisqu'il n'y a qu'un fil, nous sommes sûrs qu'il n'y a aucune chance de condition de course.

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);
});
12
répondu Rsh 2015-11-15 10:54:56

J'espère que cela va résoudre votre problème, je travaille habituellement avec cela quand j'ai besoin d'exécuter 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();
}
8
répondu Adnene Belfodil 2015-11-12 14:31:14

Ma solution sans Promesse (cela garantit que chaque action est terminée avant le début de la suivante):

Array.prototype.forEachAsync = function (callback, end) {
        var self = this;
    
        function task(index) {
            var x = self[index];
            if (index >= self.length) {
                end()
            }
            else {
                callback(self[index], index, self, function () {
                    task(index + 1);
                });
            }
        }
    
        task(0);
    };
    
    
    var i = 0;
    var myArray = Array.apply(null, Array(10)).map(function(item) { return i++; });
    console.log(JSON.stringify(myArray));
    myArray.forEachAsync(function(item, index, arr, next){
      setTimeout(function(){
        $(".toto").append("<div>item index " + item + " done</div>");
        console.log("action " + item + " done");
        next();
      }, 300);
    }, function(){
        $(".toto").append("<div>ALL ACTIONS ARE DONE</div>");
        console.log("ALL ACTIONS ARE DONE");
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="toto">

</div>
2
répondu jackstrapp 2017-04-26 21:19:58

avec ES2018 vous pouvez utiliser des itérateurs async:

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');
}
1
répondu Krzysztof Grzybek 2018-08-15 10:29:32

c'est la solution pour le noeud.js qui est asynchrone.

utilisant le paquet npm async.

(JavaScript) synchroniser la boucle forEach avec les callbacks à l'intérieur de

0
répondu Adam Mendoza 2017-05-23 11:47:29

que diriez-vous de setInterval, pour vérifier le nombre d'itérations complet, apporte la garantie. pas sûr qu'il ne va pas surcharger la portée, mais je l'utilise et semble être celui

_.forEach(actual_JSON, function (key, value) {

     // run any action and push with each iteration 

     array.push(response.id)

});


setInterval(function(){

    if(array.length > 300) {

        callback()

    }

}, 100);
0
répondu Tino Costa 'El Nino' 2017-04-25 00:42:19

ma solution:

//Object forEachDone

Object.defineProperty(Array.prototype, "forEachDone", {
    enumerable: false,
    value: function(task, cb){
        var counter = 0;
        this.forEach(function(item, index, array){
            task(item, index, array);
            if(array.length === ++counter){
                if(cb) cb();
            }
        });
    }
});


//Array forEachDone

Object.defineProperty(Object.prototype, "forEachDone", {
    enumerable: false,
    value: function(task, cb){
        var obj = this;
        var counter = 0;
        Object.keys(obj).forEach(function(key, index, array){
            task(obj[key], key, obj);
            if(array.length === ++counter){
                if(cb) cb();
            }
        });
    }
});

exemple:

var arr = ['a', 'b', 'c'];

arr.forEachDone(function(item){
    console.log(item);
}, function(){
   console.log('done');
});

// out: a b c done
0
répondu Gabor 2017-06-16 09:50:54

j'essaie de Moyen Facile de le résoudre, de le partager avec vous :

let counter = 0;
            arr.forEach(async (item, index) => {
                await request.query(item, (err, recordset) => {
                    if (err) console.log(err);

                    //do Somthings

                    counter++;
                    if(counter == tableCmd.length){
                        sql.close();
                        callback();
                    }
                });

request est une fonction de la bibliothèque mssql dans le noeud js. Cela peut remplacer chaque fonction ou Code que vous voulez. GoodLuck

0
répondu HamidReza Heydari 2018-04-09 06:11:38
var i=0;
const waitFor = (ms) => 
{ 
  new Promise((r) => 
  {
   setTimeout(function () {
   console.log('timeout completed: ',ms,' : ',i); 
     i++;
     if(i==data.length){
      console.log('Done')  
    }
  }, ms); 
 })
}
var data=[1000, 200, 500];
data.forEach((num) => {
  waitFor(num)
})
0
répondu Nilesh Pawar 2018-05-16 05:12:51

une solution simple serait comme suit

function callback(){console.log("i am done");}

["a", "b", "c"].forEach(function(item, index, array){
    //code here
    if(i == array.length -1)
    callback()
}
-1
répondu molham556 2018-01-22 14:36:40

vous ne devriez pas avoir besoin d'un rappel pour itérer à travers une liste. Il suffit d'ajouter l'appel end() après la boucle.

posts.forEach(function(v, i){
   res.write(v + ". Index " + i);
});
res.end();
-2
répondu azz 2013-09-24 13:38:32