web-dev-qa-db-fra.com

Rappel après tous les rappels pourChaque asynchrones sont terminés

Comme le titre l'indique. Comment puis-je faire cela? 

Je souhaite appeler whenAllDone() une fois 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();
  }
);

Possible de le faire fonctionner comme ça? Quand le second argument de forEach est une fonction de rappel qui s'exécute une fois qu'il est passé par toutes les itérations?

Production attendue:

3 done
1 done
2 done
All done!
183
Dan Andreasson

Array.forEach ne fournit pas cette finesse (oh si c’est le cas), mais il existe plusieurs façons de réaliser ce que vous voulez:

Utiliser 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 éléments sont traités avant d'appeler le rappel "terminé". Vous devez utiliser un compteur qui est mis à jour dans le rappel. Selon la valeur du paramètre index, la garantie n'est pas identique, car l'ordre de retour des opérations asynchrones n'est pas garanti.

Utiliser ES6 Promises

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

  1. Traiter toutes les requêtes 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 rapidement que 1)

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

Utiliser une bibliothèque async

Il existe d’autres bibliothèques asynchrones, async étant la plus populaire, offrant des mécanismes pour exprimer ce que vous voulez.


Le corps de la question a été modifié pour supprimer l'ancien exemple de code synchrone, c'est pourquoi j'ai mis à jour ma réponse afin de préciser . L'exemple d'origine utilisait un code similaire à Synchrone pour modéliser le comportement asynchrone.

array.forEach est synchrone et est donc res.write, vous pouvez donc simplement rappeler votre correspondant après votre appel à foreach:

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

  res.end();
317
Nick Tomlin

si vous rencontrez des fonctions asynchrones et que vous voulez vous assurer qu'avant d'exécuter le code, sa tâche est terminée, nous pouvons toujours utiliser la fonctionnalité 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 une fonction à exécuter une fois que chaque tâche est terminée . asynchrone est la fonction asynchrone exécutée dans foreach.

j'espère que cela t'aides.

21

Il est étrange de voir combien de réponses incorrectes ont été données à 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 telles que async, je pense que votre meilleur pari est de sauvegarder la longueur de la liste et de la décrémenter après chaque itération. Comme il n'y a qu'un seul fil, nous sommes sûrs qu'il n'y a aucune chance de condition de concurrence.

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

J'espère que cela résoudra votre problème, je travaille généralement avec cela lorsque je dois 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();
}
10
Adnene Belfodil

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
jackstrapp
 var counter = 0;
 var listArray = [0, 1, 2, 3, 4];
 function callBack() {
     if (listArray.length === counter) {
         console.log('All Done')
     }
 };
 listArray.forEach(function(element){
     console.log(element);
     counter = counter + 1;
     callBack();
 });
2
Hardik Shimpi

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');
}
1
Krzysztof Grzybek

C'est la solution pour Node.js qui est asynchrone. 

en utilisant le paquet async npm.

(JavaScript) Synchronisation de chaque boucle avec des rappels à l'intérieur

0
Adam Mendoza

J'essaie Easy Way pour le résoudre, je le partage 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

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
Gabor
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
Nilesh Pawar