Callback po zakończeniu wszystkich asynchronicznych połączeń zwrotnych forEach

Jak sugeruje tytuł. Jak to zrobić?

Chcę wywołać whenAllDone() po tym, jak pętla forEach przejdzie przez każdy element i zrobi asynchroniczne przetwarzanie.

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

Możliwe, aby to działało w ten sposób? Kiedy drugim argumentem forEach jest funkcja callback, która działa po przejściu przez wszystkie iteracje?

Oczekiwany wynik:

3 done
1 done
2 done
All done!
Author: Alexis Wilke, 2013-09-24

13 answers

Array.forEach nie zapewnia tego nicości (o, gdyby tak było), ale jest kilka sposobów, aby osiągnąć to, co chcesz:

Używanie prostego licznika

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

var itemsProcessed = 0;

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

(dzięki @vanuan i innym) to podejście gwarantuje, że wszystkie elementy są przetwarzane przed wywołaniem wywołania zwrotnego "gotowe". Podejście sugerowane przez Emila, choć z mojego doświadczenia typowo skuteczne, nie daje takiej samej gwarancji.

Używanie obietnic ES6

(biblioteka promise może być używana dla starszych przeglądarki):

  1. Przetwarza wszystkie żądania gwarantujące synchroniczne wykonanie (np. 1, 2, 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. Przetwarza wszystkie żądania asynchroniczne bez" synchronicznego " wykonania (2 może zakończyć się szybciej niż 1)

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

Korzystanie z biblioteki asynchronicznej

Istnieją inne biblioteki asynchroniczne, async są najbardziej popularne, które zapewniają mechanizmy wyrażania tego, co chcesz.

Edytuj

Treść Pytania została zredagowana aby usunąć wcześniej synchroniczny przykładowy kod, więc zaktualizowałem moją odpowiedź, aby wyjaśnić. Oryginalny przykład używał kodu synchronicznego do modelowania asynchronicznego zachowania, więc zastosowano następujące:

array.forEach jest synchroniczne i tak jest res.write, więc możesz po prostu umieścić swoje callback po połączeniu do foreach:

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

  res.end();
 292
Author: Nick Tomlin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:21

Jeśli napotkasz funkcje asynchroniczne i chcesz się upewnić, że przed wykonaniem kodu zakończy swoje zadanie, zawsze możemy użyć funkcji callback.

Na przykład:

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

Uwaga: functionAfterForEach jest funkcją wykonywaną po zakończeniu zadań foreach. asynchroniczna jest funkcją asynchroniczną wykonywaną wewnątrz foreach.

Mam nadzieję, że to pomoże.
 21
Author: Emil Reña Enriquez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-10 01:49:27

To dziwne, ile błędnych odpowiedzi zostało udzielonych asynchronicznej przypadku! Można po prostu pokazać, że sprawdzanie indeksu nie zapewnia oczekiwanego zachowania:

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

Wyjście:

4000 started
2000 started
1: 2000
0: 4000

Jeśli sprawdzimy index === array.length - 1, wywołanie callback zostanie wywołane po zakończeniu pierwszej iteracji, podczas gdy pierwszy element jest jeszcze w toku!

Aby rozwiązać ten problem bez korzystania z zewnętrznych bibliotek, takich jak async, myślę, że najlepiej jest zapisać długość listy i zmniejszyć, jeśli po każdym iteracja. Ponieważ jest tylko jeden wątek, jesteśmy pewni, że nie ma szans na stan rasy.

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
Author: Rsh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-15 10:54:56

Mam nadzieję, że to rozwiąże twój problem, zwykle pracuję z tym, gdy muszę wykonać forEach z asynchronicznymi zadaniami wewnątrz.

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

Z

function doAsynchronousFunction(entry,callback){
       //asynchronousjob with entry
       callback();
}
 8
Author: Adnene Belfodil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-12 14:31:14

Moje rozwiązanie bez obietnicy (zapewnia to, że każda akcja kończy się przed rozpoczęciem następnej):

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
Author: jackstrapp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-26 21:19:58

Z ES2018 możesz używać iteratorów asynchronicznych:

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
Author: Krzysztof Grzybek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-15 10:29:32

Jest To rozwiązanie dla Node.js, który jest asynchroniczny.

Używanie pakietu async npm.

(JavaScript) Synchronizacja pętli forEach z wywołaniami zwrotnymi wewnątrz

 0
Author: Adam Mendoza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:29

Jak o setInterval, aby sprawdzić pełną liczbę iteracji, przynosi gwarancję. Nie wiem, czy nie przeciąży lunety, ale używam jej i wydaje się być tą

_.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
Author: Tino Costa 'El Nino',
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-25 00:42:19

Moje rozwiązanie:

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

Przykład:

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

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

// out: a b c done
 0
Author: Gabor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-16 09:50:54

Staram się łatwo to rozwiązać, podzielić się z Tobą:

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 jest funkcją biblioteki mssql w Node js. Może to zastąpić każdą funkcję lub kod, który chcesz. GoodLuck

 0
Author: HamidReza Heydari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
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
Author: Nilesh Pawar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-16 05:12:51

Proste rozwiązanie byłoby jak follow

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

["a", "b", "c"].forEach(function(item, index, array){
    //code here
    if(i == array.length -1)
    callback()
}
 -1
Author: molham556,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-22 14:36:40

Nie powinieneś potrzebować połączenia zwrotnego do iteracji listy. Wystarczy dodać wywołanie end() po pętli.

posts.forEach(function(v, i){
   res.write(v + ". Index " + i);
});
res.end();
 -2
Author: azz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-24 13:38:32