3

I'm trying to put a delay between each iteration in a loop using async await. I've got a helper sleep function:

const sleep = ms => {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

And this is correctly waiting between each loop:

for (let i = 0; i < 5; i++) {
    console.log('waiting')
    await sleep(1000)
}

However, this is not waiting between each loop:

[0, 1, 2, 3, 4].forEach(async () => {
    console.log('waiting')
    await sleep(1000)
});

How can I modify the forEach code block to behave as the regular for loop block with delays between each iteration of the for loop?

2
  • 1
    Since you're using a modern version of ECMAScript (with async/await), you probably don't want to use .forEach. It was from before the language had proper iterators, which you can now use with for (let/const value of array) (instead of a for-in loop as you have, which are also more-or-less obsolete). Commented Apr 1, 2019 at 23:06
  • To make this work, forEach itself would have to await the promise returned by the callback. But .forEach completely ignores the return value. Commented Apr 1, 2019 at 23:26

3 Answers 3

1

You could theoretically build up a promise chain, thats slightly more beautiful with reduce, but the same pattern can also be done with forEach:

[0, 1, 2, 3, 4].reduce(async (previous) => {
  await previous;
  console.log('waiting')
  await sleep(1000)
});

But ... why not just use a for loop?

Sign up to request clarification or add additional context in comments.

5 Comments

Actually I was looking to do what you suggested with reduce...I don't know why I used forEach in my example And no real good reason to use reduce over a for loop other than it being slightly cleaner for my purpose so I was just wondering if there was an easy way to achieve this that I was overlooking
This solution will use tons of memory for a large array. Only pointing that out for the questioner.
@andrew I can't think of a situation were a forEach would be "cleaner" than a for though ...
To clarify I meant that using a reduce would be cleaner than a for loop. I shouldn't have mentioned forEach that was a bad example (I was just randomly pulling out a looping function with a callback).
But yes thanks for the comments, I ended up running out of memory as you guys suggested might happen, so I'll go with a regular for loop.
1

If you prefer methods rather than loops (which I usually do just aesthetically) you could pull in a third party module I maintain called async-af.

Among other things, it provides an asynchronous-friendly sequential forEach:

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

AsyncAF([0, 1, 2, 3, 4]).series.forEach(async () => {
  console.log('waiting');
  await sleep(1000);
});
<script src="https://unpkg.com/[email protected]/index.js"></script>

Of course, you could also just use a simple for...of loop:

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

(async () => {
  for (const _ of [0, 1, 2, 3, 4]) {
    console.log('waiting');
    await sleep(1000);
  }
})();

As for why Array.prototype.forEach doesn't work the way you'd expect, take this overly simplified implementation (here's a fuller one):

const forEach = (arr, fn) => {
  for (let i = 0; i < arr.length; i++) {
    // nothing is awaiting this function call
    fn(arr[i], i, arr);
    // i is then synchronously incremented and the next function is called
  }
  // return undefined
};

forEach([0, 1, 2, 3, 4], async () => {
  console.log('waiting');
  await delay(1000);
});

As you can see, Array.prototype.forEach synchronously calls the given callback function on each element. That's why you see all five waiting logs almost immediately. See this question for more information.

Comments

1

First of all, for...of is the way to go.

However if you die hard want a .forEach method, you can do something like this:

const sleep = ms => {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

/* Create your own async forEach */
Array.prototype.asyncForEach = async function asyncForEach(callback, ctx){
  const len = this.length;
  for (let i = 0; i < len; i++)
    await callback.call(ctx, this[i], i, this);
};

[0, 1, 2, 3, 4].asyncForEach(async function(n){
  await sleep(1000);
  console.log(n);
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.