297

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!
Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Dan Andreasson
  • 15,380
  • 5
  • 29
  • 30
  • 17
    It would be nice if standard array `forEach` method had `done` callback parameter and `allDone` callback! – Vanuan Dec 23 '15 at 18:24
  • 36
    It's a real shame something so simple requires so much wrestling in JavaScript. – Ali Feb 10 '16 at 09:07

17 Answers17

515

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();
Cheeso
  • 189,189
  • 101
  • 473
  • 713
Nick Tomlin
  • 28,402
  • 11
  • 61
  • 90
  • 36
    Note, however, that if there is asynchronous stuff inside the forEach (e.g. you are looping through an array of URLs and doing an HTTP GET on them), there's no guarantee that res.end will be called last. – AlexMA Sep 24 '13 at 14:17
  • In order to fire a callback after an async action is performed in a loop you can use the async utility's each method: https://github.com/caolan/async#each – elkelk Jul 24 '14 at 13:43
  • 7
    why not just `if(index === array.length - 1)` and remove `itemsProcessed ` – Amin Jafari Nov 17 '16 at 21:56
  • 6
    @AminJafari because the asynchronous calls may not resolve in the exact order that they are registered (say you are calling out to a server and it stalls slightly on the 2nd call but processes the last call fine). The last asynchronous call could resolve before the previous ones. Mutating a counter guards against this since _all_ callbacks must fire regardless of the order in which they resolve. – Nick Tomlin Nov 19 '16 at 00:50
  • why not `if(index === array.length) {` instead of `if(itemsProcessed === array.length) {`? it can save memory of one variable and processing of increment – Inzamam Malik May 30 '17 at 20:26
  • @NickTomlin very nice. But I don't think you need `itemsProcessed` because with `forEach` you can get the `index` of each element with you did pass in as the second argument on the `forEach` callback. You can just simple check `if (index == array.length - 1)`. Cheers! – tkhuynh Oct 07 '17 at 08:04
  • what is `cb` variable for? Seems like it is undefined? – ellaRT May 30 '18 at 02:34
  • Please consider also the situation when array length is zero, in this case, the callback would never be called – Saeed Ir May 02 '20 at 21:21
  • Since implementation of rxjs, looping through array of URLs and doing any HTTP request synchronously can be easily implemented with forkJoin – David Ćeranić Jun 17 '20 at 19:10
  • Bravo, this was huge in helping me solve some asynchronous functions firing inside of a for each loop and looping through object keys. The reduce function is great! Thank you for introducing us to that with a practical example. – Willie Dec 08 '20 at 22:41
34

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.

vvvvv
  • 25,404
  • 19
  • 49
  • 81
Emil Reña Enriquez
  • 2,929
  • 1
  • 29
  • 32
  • 4
    hi all since the latest updates on ES6 with Promises and Async/await it's better to utilise Promises and Async/await feature. this solution would be obsolete by now. – Emil Reña Enriquez Sep 02 '19 at 19:31
18

Hope this will fix your problem, i usually work with this when i need to execute forEach with asynchronous tasks inside.

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

with

function doAsynchronousFunction(entry,callback){
       //asynchronousjob with entry
       callback();
}
Adnene Belfodil
  • 191
  • 1
  • 5
  • I was having a similar issue in my Angular 9 code and this answer did the trick for me. Though @Emil Reña Enriquez answer also worked for me but I find this to be more accurate and simple answer for this problem. – omostan May 08 '20 at 13:37
18

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);
});
Rsh
  • 7,214
  • 5
  • 36
  • 45
  • 1
    That's probably the only solution. Does async library also use counters? – Vanuan Dec 23 '15 at 18:06
  • 1
    Although other solutions do the job, this is most compelling because it doesn't require chaining or added complexity. K.I.S.S. – 4Z4T4R Sep 06 '17 at 01:26
  • Please consider also the situation when array length is zero, in this case, the callback would never be called – Saeed Ir May 02 '20 at 21:22
11

With ES2018 you can use async iterators:

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');
}
Krzysztof Grzybek
  • 8,818
  • 2
  • 31
  • 35
3

My solution without Promise (this ensures that every action is ended before the next one begins):

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>
jackstrapp
  • 195
  • 10
1

There are many solutions and ways to achieve this on this thread!.

But, if you need do this with map and async/await then here it is

// Execution Starts
console.log("start")

// The Map will return promises
// the Execution will not go forward until all the promises are resolved.
await Promise.all(
    [1, 2, 3].map( async (item) => {
        await asyncFunction(item)
    })
)

// Will only run after all the items have resolved the asynchronous function. 
console.log("End")

The output will be something like this! May vary based on the asynchronous function.

start
2
3
1
end

Note: If you use await in a map, it will always return promises array.

Ashfaq nisar
  • 2,500
  • 1
  • 12
  • 22
0

This is the solution for Node.js which is asynchronous.

using the async npm package.

(JavaScript) Synchronizing forEach Loop with callbacks inside

Community
  • 1
  • 1
Adam Mendoza
  • 5,419
  • 2
  • 25
  • 31
0

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

Example:

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

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

// out: a b c done
Gabor
  • 11
  • 4
0

I try Easy Way to resolve it, share it with you :

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 is Function of mssql Library in Node js. This can replace each function or Code u want. GoodLuck

0
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)
})
Nilesh Pawar
  • 655
  • 7
  • 12
0
 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();
 });
Hardik Shimpi
  • 412
  • 7
  • 13
0

This piece of code worked for me, thanks to Nick Tomlin answer:

$(document).ready(function(){

    let arr = ["a1", "a2", "a3","a1", "a2", "a3","a1", "a2", "a3","a1", "a2", "a3","a1", "a2", "a3"];
    let motherArray = new Array();


    let arrayPushDone = arr.reduce((prevPromise, currentValue)=>{
        return prevPromise.then(()=>{
            console.log("currentValue: ", currentValue);
            return new Promise((resolve) =>resolve(motherArray.push(currentValue)));
        });
    }, Promise.resolve());

    arrayPushDone.then(()=>console.log("array push done: ", motherArray));
});
zahra_oveyedzade
  • 988
  • 10
  • 17
0

Use async/await

async/await documentation

You can now call your function with the async declaration and await keyword to pause execution inside your function until a statement is resolved.

By labeling your containing function call with async you're able to prevent lines of code below your await statement from running until it has resolved. Note, if your await statement fails your function will not continue to execute.

To use the original code example:

async wrapperFunction() {
  await [1, 2, 3].forEach(item => {  
    console.log(item + " done"); // Iteratively process individual item in array
  });
  console.log("All done"); // Executes once the awaited forEach() completes
};

wrapperFunction();

Console output:

1 done
2 done
3 done
All done
Jared Eddy
  • 66
  • 8
-1
//First Example
save() {
    this.holdImageData.forEach((val,i) => {
        this.UploadToMinio(val.file, val.index, res => {
            if (res && i+1 == this.holdImageData.length) {
                this.FinallySave();
            }
        })
    })
}

UploadToMinio(files, index, callback) {
    this._fileUploadService.uploadFile(files[0], files[0].name, 'rms').subscribe(data => {
        if (data) {
            console.log('data >>> ', data);
            callback(true);
        }
    })
}

FinallySave() {}

//Second Example
var sum = 0; // It can be global variable
startFunction() {
    this.sumFunction(2, 4, res => {
        if (res == true && sum == 6) {
            this.saveFunction();
        } else {
            //call to another function
        }
    })
}

sumFunction(num1, num2, callback) {
    if ((num1 + num2) == 6) {
        callback(true)
    }
    callback(false);
}
-4

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();
azz
  • 5,852
  • 3
  • 30
  • 58
  • 3
    No. The OP emphasized that asynchronous logic would execute for each iteration. `res.write` is NOT an asynchronous operation, so your code won't work. – Jim G. Aug 09 '16 at 15:55
-4

How about setInterval, to check for complete iteration count, brings guarantee. not sure if it won't overload the scope though but I use it and seems to be the one

_.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);