2
votes

I'm trying to execute a function after a forEach loop has completed all iterations.

This answer provides an interesting solution, but I can't get it to work.

Here's the code I adapted, creating a simple asyncFunction().

function callback () { console.log('all done'); }
function asyncFunction(item) {
  console.log("in async function, item is " + item)
}
var itemsProcessed = 0;

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

As visible in this JSfiddle, the script correctly executes the async function, but fails to enter the part which increments itemsProcessed and should trigger the callback() function.

I'm not too familiar with the fat arrow functions, so maybe the error comes from their usage.

Can anyone explain why the script isn't behaving as expected?

3
You are passing a function as second argument to asyncFunction() when you call it but function declaration doesn't expect second argument and never calls the callback passed to itcharlietfl
That makes sense, thanks. However I'm not sure which argument to declare second. Obviously asyncFunction(item, ()) doesn't work. Could you please explain more explicitly how to tackle this second argument?sc28

3 Answers

5
votes

This is a case where the more modern approach is to use promises

function asyncFunction(item) {
   // return a promise
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("in async function, item is " + item)
      // resolve promise when data is ready
      resolve(item)
    }, Math.random()*2000)// random delay to stagger order of returns

  })

}

// create array of promises
let promiseArray = [1, 2, 3].map(asyncFunction);

// runs when all promises are resolved
Promise.all(promiseArray).then(results => {
  console.log('all done')
  // results array will be in same order as original array
  console.log('results are: ', results)
})
.as-console-wrapper {max-height: 100%!important;top:0}
2
votes

Because you want to pass a callback function as 2nd argument to asyncFunction, you need to specify that there'll be a callback function as 2nd argument, and you need to call that like this:

function asyncFunction(item, cb) {
  console.log("in async function, item is " + item)
  cb()
}

Also, your code can be rewrite to make it easier to understand the use of callback function. Your code:

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

is the same as:

[1, 2, 3].forEach((item, index, array) => {
  function cbFunc() {
    itemsProcessed++;
    console.log("in callback area, itemsProcessed is " + itemsProcessed )
    if(itemsProcessed === array.length) {
      callback();
    }
  }
  asyncFunction(item, cbFunc);
});
1
votes

Map each of the elements to a Promise, then use Promise.all().

Promise.all([1, 2, 3].map(async num => num));

Of course, you can do something more sophisticated inside of the async function if you want.

Promise.all([1, 2, 3].map(num => 
{
    return new Promise((reject, resolve) =>
    {
        setTimeout(() => resolve(num), 5000);
    })
}));

And if the code you're executing is synchronous or involves a timeout, then use the Promise constructor instead of an async function.