webentwicklung-frage-antwort-db.com.de

Callback, nachdem alle asynchronen forEach-Callbacks abgeschlossen sind

Wie der Titel schon sagt. Wie mache ich das? 

Ich möchte whenAllDone() aufrufen, nachdem die forEach-Schleife jedes Element durchlaufen hat und eine asynchrone Verarbeitung ausgeführt hat.

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

Möglich, dass es so funktioniert? Wenn das zweite Argument für forEach eine Rückruffunktion ist, die ausgeführt wird, wenn alle Iterationen durchlaufen wurden?

Erwartete Ausgabe:

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

Array.forEach bietet diese Sicherheit nicht (oh wenn es so wäre), aber es gibt mehrere Möglichkeiten, um das zu erreichen, was Sie wollen:

Verwenden Sie einen einfachen Zähler

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

var itemsProcessed = 0;

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

(Dank an @vanuan und andere) Dieser Ansatz garantiert, dass alle Elemente verarbeitet werden, bevor der Rückruf "done" aufgerufen wird. Sie müssen einen Zähler verwenden, der im Callback aktualisiert wird. Abhängig vom Wert des Index-Parameters bietet sich nicht die gleiche Garantie, da die Reihenfolge der Rückgabe der asynchronen Vorgänge nicht garantiert wird.

Verwenden von ES6-Versprechen

(Eine Versprechenbibliothek kann für ältere Browser verwendet werden):

  1. Verarbeiten Sie alle Anforderungen, die eine synchrone Ausführung gewährleisten (z. B. 1 dann 2 dann 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. Verarbeiten Sie alle asynchronen Anforderungen ohne "synchrone" Ausführung (2 können schneller abgeschlossen werden als 1)

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

Verwenden einer asynchronen Bibliothek

Es gibt andere asynchrone Bibliotheken, wobei async die populärste ist, die Mechanismen zur Verfügung stellen, um auszudrücken, was Sie wollen.


Der Hauptteil der Frage wurde bearbeitet, um den zuvor synchronen Beispielcode zu entfernen. Daher habe ich meine Antwort aktualisiert, um klarer zu stellen .. Das ursprüngliche Beispiel verwendete synchronen Code, um asynchrones Verhalten zu modellieren. Daher wurde Folgendes angewendet:

array.forEach ist synchronous und so ist res.write, so dass Sie Ihren Rückruf nach Ihrem Anruf bei foreach einfach setzen können:

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

  res.end();
317
Nick Tomlin

wenn Sie auf asynchrone Funktionen stoßen und sicherstellen möchten, dass der Task vor Ausführung des Codes ausgeführt wird, können Sie immer die Rückruffunktion verwenden.

zum Beispiel:

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

anmerkung: functionAfterForEach ist eine Funktion, die ausgeführt wird, nachdem foreach-Aufgaben abgeschlossen sind . asynchron ist die asynchrone Funktion, die in foreach ausgeführt wird.

hoffe das hilft.

21

Es ist merkwürdig, wie viele falsche Antworten auf den asynchronous -Fall! .__ gegeben wurden. Es kann einfach gezeigt werden, dass das Überprüfen des Index kein erwartetes Verhalten liefert:

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

ausgabe:

4000 started
2000 started
1: 2000
0: 4000

Wenn wir nach index === array.length - 1 suchen, wird der Callback nach Abschluss der ersten Iteration aufgerufen, während das erste Element noch aussteht!

Um dieses Problem zu lösen, ohne externe Bibliotheken wie async zu verwenden, denke ich, ist es am besten, wenn Sie nach jeder Iteration Länge der Liste und Dekrementierung sparen. Da es nur einen Thread gibt, sind wir uns sicher, dass es keine Chance auf Rennen gibt.

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

Ich hoffe, das wird Ihr Problem beheben. Normalerweise arbeite ich damit, wenn ich forEach mit asynchronen Aufgaben ausführen muss.

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

mit 

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

Meine Lösung ohne Versprechen (dadurch wird sichergestellt, dass jede Aktion beendet wird, bevor die nächste beginnt):

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

Mit ES2018 können Sie asynchrone Iteratoren verwenden:

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

Dies ist die Lösung für Node.js, die asynchron ist. 

verwenden des asynchronen npm-Pakets.

(JavaScript) forEach Loop mit Callbacks im Inneren synchronisieren

0
Adam Mendoza

Ich versuche es mit Easy Way zu lösen, teile es mit dir:

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 ist Funktion der mssql-Bibliothek in Knoten js. Dies kann jede Funktion oder jeden Code ersetzen, den Sie wollen . GoodLuck

0

Meine Lösung:

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

Beispiel:

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