Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Callback after all asynchronous forEach callbacks are completed

As the title suggests. How do I do this?

I want to call whenAllDone() after the forEach-loop has gone through each element and done some asynchronous processing.

[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 to get it to work like this? When the second argument to forEach is a callback function which runs once it went through all iterations?

Expected output:

3 done
1 done
2 done
All done!
like image 594
Dan Andreasson Avatar asked Sep 24 '13 13:09

Dan Andreasson


People also ask

Are callback always asynchronous?

The function that takes another function as an argument is called a higher-order function. According to this definition, any function can become a callback function if it is passed as an argument. Callbacks are not asynchronous by nature, but can be used for asynchronous purposes.

Is callback function synchronous or asynchronous?

The callback is a function that's accepted as an argument and executed by another function (the higher-order function). There are 2 kinds of callback functions: synchronous and asynchronous. The synchronous callbacks are executed at the same time as the higher-order function that uses the callback.

Is forEach a callback function?

The forEach() method calls a specified callback function once for every element it iterates over inside an array.

Does forEach wait for completion?

Because forEach does not wait for each promise to resolve, all the prizes are awarded in parallel, not serial (one by one). So the loop actually finishes iterating before any of the prizes have finished been awarded (but after they have all started being awarded).

What is the difference between callback and asynchronous in JavaScript?

If you encounter asynchronous functions, and you want to make sure that before executing the code it finishes its task, we can always use the callback capability. Note: functionAfterForEach is the function to be executed after foreach tasks are finished. asynchronous is the asynchronous function executed inside foreach.

Do I need a callback for iterating through a list?

You shouldn't need a callback for iterating through a list. Just add the end () call after the loop. posts.forEach (function (v, i) { res.write (v + ". Index " + i); }); res.end ();

What is the difference between functionafterforeach () and asynchronous function?

Note: functionAfterForEach is the function to be executed after foreach tasks are finished. asynchronous is the asynchronous function executed inside foreach. Show activity on this post. It's odd how many incorrect answers has been given to asynchronous case!

How to get callback to call on first element from array?

If we check for index === array.length - 1, callback will be called upon completion of first iteration, whilst first element is still pending! To solve this problem without using external libraries such as async, I think your best bet is to save length of list and decrement if after each iteration.


Video Answer


3 Answers

Array.forEach does not provide this nicety (oh if it would) but there are several ways to accomplish what you want:

Using a simple counter

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

var itemsProcessed = 0;

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

(thanks to @vanuan and others) This approach guarantees that all items are processed before invoking the "done" callback. You need to use a counter that gets updated in the callback. Depending on the value of the index parameter does not provide the same guarantee, because the order of return of the asynchronous operations is not guaranteed.

Using ES6 Promises

(a promise library can be used for older browsers):

  1. Process all requests guaranteeing synchronous execution (e.g. 1 then 2 then 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. Process all async requests without "synchronous" execution (2 may finish faster than 1)

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

Using an async library

There are other asynchronous libraries, async being the most popular, that provide mechanisms to express what you want.

Edit

The body of the question has been edited to remove the previously synchronous example code, so i've updated my answer to clarify. The original example used synchronous like code to model asynchronous behaviour, so the following applied:

array.forEach is synchronous and so is res.write, so you can simply put your callback after your call to foreach:

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

  res.end();
like image 60
Nick Tomlin Avatar answered Oct 17 '22 07:10

Nick Tomlin


If you encounter asynchronous functions, and you want to make sure that before executing the code it finishes its task, we can always use the callback capability.

For example:

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

Note: functionAfterForEach is the function to be executed after foreach tasks are finished. asynchronous is the asynchronous function executed inside foreach.

like image 33
Emil Reña Enriquez Avatar answered Oct 17 '22 05:10

Emil Reña Enriquez


It's odd how many incorrect answers has been given to asynchronous case! It can be simply shown that checking index does not provide expected behavior:

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

output:

4000 started
2000 started
1: 2000
0: 4000

If we check for index === array.length - 1, callback will be called upon completion of first iteration, whilst first element is still pending!

To solve this problem without using external libraries such as async, I think your best bet is to save length of list and decrement if after each iteration. Since there's just one thread we're sure there no chance of race condition.

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);
});
like image 35
Rsh Avatar answered Oct 17 '22 07:10

Rsh