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!
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):
-
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'))
-
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.
Modifierle 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();
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.
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);
});
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();
}
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>
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');
}
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
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);
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
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
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)
})
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()
}
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();