2

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?

sc28
  • 1,163
  • 4
  • 26
  • 48
  • 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 it – charlietfl May 20 '18 at 00:24
  • 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 May 20 '18 at 00:28

3 Answers3

5

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}
charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • Mr. Charlie, Thank you very much! I learnt from this snippet and moved ahead in my task. In this virtual world where we can't meet and shake hands. May you be blessed with my good karma. – Om Sao Oct 16 '20 at 04:30
2

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);
});
Zep
  • 38
  • 5
1

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.

laptou
  • 6,389
  • 2
  • 28
  • 59