97

Sometimes I need to wait for a .forEach() method to finish, mostly on 'loader' functions. This is the way I do that:

$q.when(array.forEach(function(item){ 
    //iterate on something 
})).then(function(){ 
    //continue with processing 
});

I can't help but feel that this isn't the best way to wait for a .forEach() to finish. What is the best way to do this?

vvvvv
  • 25,404
  • 19
  • 49
  • 81
Jaap Weijland
  • 3,146
  • 5
  • 23
  • 31

16 Answers16

140

If there is no asynchronous code inside the forEach, forEach is not asynchronous, for example in this code:

array.forEach(function(item){ 
    //iterate on something 
});
alert("Foreach DONE !");

you will see the alert after forEach finished.

Otherwise (You have something asynchronous inside), you can wrap the forEach loop in a Promise:

var bar = new Promise((resolve, reject) => {
    foo.forEach((value, index, array) => {
        console.log(value);
        if (index === array.length -1) resolve();
    });
});

bar.then(() => {
    console.log('All done!');
});

Credit: @rolando-benjamin-vaz-ferreira

Ismail RBOUH
  • 10,292
  • 2
  • 24
  • 36
  • 49
    This is OK if there is no async processing inside the loop. – jarmod Apr 04 '18 at 16:51
  • 3
    Alert will trigger before foreach finishes. It doesn't work. – Raghu Sep 18 '19 at 19:50
  • Async is not added. Also(when async code exists inside loop), this won't guarantee if the last item finishes first, as a result resolve() will occur even before all the threads/items have finished. – RollerCosta Aug 19 '20 at 08:56
  • 1
    Doesn't work if there is async code inside the loop. – mikebrsv Jun 14 '21 at 19:32
  • @mikebrsv please read after "Otherwise (You have something asynchronous inside)". Thanks. – Ismail RBOUH Jun 14 '21 at 22:31
  • 2
    This doesn't work, the forEach loop is not guaranteed to run in order, so when the index === array.length check passes, there is no guarantee that all of the items have been processed. I think @PJ3 answer is the simplest and works. – atlas_scoffed Jun 16 '21 at 04:08
  • you are my hero – Abadis Aug 07 '21 at 14:02
  • .forEach for Arrays are guaranteed to run in order. MDN: "The forEach() method is an iterative method. It calls a provided callbackFn function once for each element in an array in ascending-index order. " – user3504575 Feb 07 '23 at 18:17
123

The quickest way to make this work using ES6 would be just to use a for..of loop.

const myAsyncLoopFunction = async (array) => {
  const allAsyncResults = []

  for (const item of array) {
    const asyncResult = await asyncFunction(item)
    allAsyncResults.push(asyncResult)
  }

  return allAsyncResults
}

Or you could loop over all these async requests in parallel using Promise.all() like this:

const myAsyncLoopFunction = async (array) => {
  const promises = array.map(asyncFunction)
  await Promise.all(promises)
  console.log(`All async tasks complete!`)
}
honk31
  • 3,895
  • 3
  • 31
  • 30
Douglas Rosebank
  • 1,687
  • 2
  • 13
  • 11
71
var foo = [1,2,3,4,5,6,7,8,9,10];

If you're actually doing async stuff inside the loop, you can wrap it in a promise ...

var bar = new Promise((resolve, reject) => {
    foo.forEach((value, index, array) => {
        console.log(value);
        if (index === array.length -1) resolve();
    });
});

bar.then(() => {
    console.log('All done!');
});
  • 5
    This will incorrectly resolve the promise if, for example, element with key 1 takes longer to process than element with key 2. – akrz Oct 08 '19 at 12:56
  • 6
    This does not work correctly! as @akrz said, this only works if the promise with the highest index also takes the longest. That might *never* be the case. – mneumann Oct 15 '19 at 15:18
25

If you have an async task inside a loop and you want to wait. you can use for await

for await (const i of images) {
    let img = await uploadDoc(i);
};

let x = 10; //this executes after
PJ3
  • 3,870
  • 1
  • 30
  • 34
12

Use for of instead of forEach. Like this:

for (const item of array) {
  //do something
}
console.log("finished");

"finished" will be logged after finishing the loop.

Aqeel
  • 804
  • 1
  • 11
  • 15
8

forEach() doesn't return anything, so a better practice would be map() + Promise.all()

var arr = [1, 2, 3, 4, 5, 6]

var doublify = (ele) => {
  return new Promise((res, rej) => {
    setTimeout(() => {
        res(ele * 2)
    }, Math.random() ); // Math.random returns a random number from 0~1
  })
}

var promises = arr.map(async (ele) => {
  // do some operation on ele
  // ex: var result = await some_async_function_that_return_a_promise(ele)
  // In the below I use doublify() to be such an async function

  var result = await doublify(ele)
  return new Promise((res, rej) => {res(result)})
})

Promise.all(promises)
.then((results) => {
  // do what you want on the results
  console.log(results)
})

output

Kuan-Yu Lin
  • 934
  • 7
  • 9
4

A universal solution for making sure that all forEach() elements finished execution.

const testArray = [1,2,3,4]
let count = 0

await new Promise( (resolve) => {
  testArray.forEach( (num) => {
    try {
      //some real logic
      num = num * 2
    } catch (e) {
      // error handling
      console.log(e)
    } fanally {
      // most important is here
      count += 1
      if (count == testArray.length) {
        resolve()
      }
    }
  })  
})

The idea is same with the answer using index to count. But in real case, if error happened, the index way cannot count correctly. So the solution is more robust.

Thx

Wei Tang
  • 151
  • 7
4
const array = [1, 2, 3];
const results = [];
let done = 0;

const asyncFunction = (item, callback) =>
  setTimeout(() => callback(item * 10), 100 - item * 10);

new Promise((resolve, reject) => {
  array.forEach((item) => {
    asyncFunction(item, (result) => {
      results.push(result);
      done++;
      if (done === array.length) resolve();
    });
  });
}).then(() => {
  console.log(results); // [30, 20, 10]
});

// or
// promise = new Promise(...);
// ...
// promise.then(...);

The order of results in the "results" array can be different than the order of items in the original array, depending on the time when the asyncFunction() finishes for each of the items.

1

Alter and check a counter at the end of every possible unique branch of code, including callbacks. Example:

const fs = require('fs');

/**
 * @description Delete files older than 1 day
 * @param {String} directory - The directory to purge
 * @return {Promise}
 */
async function purgeFiles(directory) {
  const maxAge = 24*3600000;
  const now = Date.now();
  const cutoff = now-maxAge;

  let filesPurged = 0;
  let filesProcessed = 0;
  let purgedSize = 0;

  await new Promise( (resolve, reject) => {
    fs.readdir(directory, (err, files) => {
      if (err) {
        return reject(err);
      }
      if (!files.length) {
        return resolve();
      }
      files.forEach( file => {
        const path = `${directory}/${file}`;
        fs.stat(path, (err, stats)=> {
          if (err) {
            console.log(err);
            if (++filesProcessed === files.length) resolve();
          }
          else if (stats.isFile() && stats.birthtimeMs < cutoff) {
            const ageSeconds = parseInt((now-stats.birthtimeMs)/1000);
            fs.unlink(path, error => {
              if (error) {
                console.log(`Deleting file failed: ${path} ${error}`);
              }
              else {
                ++filesPurged;
                purgedSize += stats.size;
                console.log(`Deleted file with age ${ageSeconds} seconds: ${path}`);
              }
              if (++filesProcessed === files.length) resolve();
            });
          }
          else if (++filesProcessed === files.length) resolve();
        });
      });
    });
  });

  console.log(JSON.stringify({
    directory,
    filesProcessed,
    filesPurged,
    purgedSize,
  }));
}

// !!DANGER!! Change this line! (intentional syntax error in ,')
const directory = ,'/tmp'; // !!DANGER!! Changeme
purgeFiles(directory).catch(error=>console.log(error));
Terris
  • 887
  • 1
  • 10
  • 15
0

I'm not sure of the efficiency of this version compared to others, but I used this recently when I had an asynchronous function inside of my forEach(). It does not use promises, mapping, or for-of loops:

// n'th triangular number recursion (aka factorial addition)
function triangularNumber(n) {
    if (n <= 1) {
        return n
    } else {
        return n + triangularNumber(n-1)
    }
}

// Example function that waits for each forEach() iteraction to complete
function testFunction() {
    // Example array with values 0 to USER_INPUT
    var USER_INPUT = 100;
    var EXAMPLE_ARRAY = Array.apply(null, {length: USER_INPUT}).map(Number.call, Number) // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, n_final... ] where n_final = USER_INPUT-1

    // Actual function used with whatever actual array you have
    var arrayLength = EXAMPLE_ARRAY.length
    var countMax = triangularNumber(arrayLength);
    var counter = 0;
    EXAMPLE_ARRAY.forEach(function(entry, index) {
        console.log(index+1); // show index for example (which can sometimes return asynchrounous results)

        counter += 1;
        if (triangularNumber(counter) == countMax) {

            // function called after forEach() is complete here
            completionFunction();
        } else {
            // example just to print counting values when max not reached
            // else would typically be excluded
            console.log("Counter index: "+counter);
            console.log("Count value: "+triangularNumber(counter));
            console.log("Count max: "+countMax);
        }
    });
}
testFunction();

function completionFunction() {
    console.log("COUNT MAX REACHED");
}
About7Deaths
  • 681
  • 1
  • 5
  • 16
0

I had to deal with the same problem (forEach using multiple promises inside) and none of the solutions presented at the current date were helpful for me. So I implemented a check array, were each promise updates its complete status. We have a general promise that wraps the process. We only resolve the general promise when each promise completed. Snippet code:

function WaitForEachToResolve(fields){

    var checked_fields = new Array(fields.length).fill(0);
    const reducer = (accumulator, currentValue) => accumulator + currentValue;

    return new Promise((resolve, reject) => {

      Object.keys(fields).forEach((key, index, array) => {

        SomeAsyncFunc(key)
        .then((result) => {

            // some result post process

            checked_fields[index] = 1;
            if (checked_fields.reduce(reducer) === checked_fields.length)
                resolve();
        })
        .catch((err) => {
            reject(err);
        });
      }
    )}
}
Federico Caccia
  • 1,817
  • 1
  • 13
  • 33
0

I like to use async-await instead of .then() syntax so for asynchronous processing of data, modified the answer of @Ronaldo this way -

let finalData = [];
var bar = new Promise(resolve => {
    foo.forEach((value, index) => {
        const dataToGet = await abcService.getXyzData(value);
        finalData[index].someKey = dataToGet.thatOtherKey;
        // any other processing here
        if (finalData[dataToGet.length - 1].someKey) resolve();
    });
});

await Promise.all([bar]);
console.log(`finalData: ${finalData}`);

NOTE: I've modified the if condition where it resolves the promise to meet my conditions. You can do the same in your case.

itsHarshad
  • 1,519
  • 14
  • 16
0

You can use this, because we are using async/await inside the forEach loop. You can use your own logic inside the loop.

    let bar = new Promise((resolve, reject) => {
        snapshot.forEach(async (doc) => {
            """Write your own custom logic and can use async/await
            """
            const result = await something()
            resolve(result);
        });
    });
    let test = []
    test.push(bar)
    let concepts = await Promise.all(test);
    console.log(concepts);
Vishal Sinha
  • 121
  • 1
  • 8
0

For simple compare code i like use for statement.

doit();
function doit() {

        for (var i = 0; i < $('span').length;  i++) {
            console.log(i,$('span').eq(i).text() );
            if ( $('span').eq(i).text() == "Share a link to this question"  ) { //  span number 59
                return;
            }
        }

alert('never execute');

}
user956584
  • 5,316
  • 3
  • 40
  • 50
0

If there are async (observable) method calls inside the for loop, you could use the following method:

await players.reduce(async (a, player) => {
  // Wait for the previous item to finish processing
  await a;
  // Process this item
  await givePrizeToPlayer(player);
}, Promise.resolve());

Check here: https://gist.github.com/joeytwiddle/37d2085425c049629b80956d3c618971

Manoj De Mel
  • 927
  • 9
  • 16
-1

I've been using this and it works best .forEach()

//count
var expecting = myArray.length;

myArray.forEach(function(item){

//do logic here
var item = item



//when iteration done
if (--expecting === 0) {

console.log('all done!');

}

})
Grogu
  • 2,097
  • 15
  • 36