Using async / await while looping through arrays in Javascript loop seems simple, but there’s some non-intuitive behavior to look out for when combining the two. Let’s take a look at three different examples to see what you should look out for, and which loop is best for specific use cases.

forEach

If you only take one thing away from this article, let it be this: async / await doesn’t work in Array.prototype.forEach . Let’s dive into an example to see why:

Finished!

Received Todo 2, Response: { ··· }

Received Todo 1, Response: { ··· }

Received Todo 3, Response: { ··· }

⚠️ Problem 1:

The code above will happily execute. However, notice that Finished! was logged out first despite our use of await before urls.forEach . The first problem is that you can’t await the entire loop when using forEach .

⚠️ ️Problem 2:

In addition, despite the use of await within the loop, it didn’t wait for each request to finish before executing the next one. So, the requests were logged out of order. If the first request takes longer than the following requests, it could still finish last.

For both of those reasons, forEach should not be relied upon if you’re using async / await .

Promise.all

Let’s solve the issue of waiting on the entire loop to finish. Since the await operation creates a promise under the hood, we can use Promise.all to await all the requests that were kicked during the loop to finish:

Received Todo 1, Response: { ··· }

Received Todo 2, Response: { ··· }

Received Todo 3, Response: { ··· }

Finished!

We’ve solved the issue of waiting on every request to finish before continuing onward. It also appears that we resolved the issue of the requests happening out of order, but that’s not exactly the case.

As mentioned earlier, Promise.all executes all of the promises given to it in parallel. It won’t wait for the first request to come back before executing the second, or third request. For most purposes this is fine, and it’s a very performant solution. But, if you truly need each request to happen in order, Promise.all won’t solve for that.

for...of

We now know that forEach doesn’t respect async / await at all, and Promise.all only works if the order of execution doesn’t matter. Let’s look at a solution that solves for both cases.

The for...of loop executes in the order one would expect — waiting on each previous await operation to complete before moving on to the next:

Received Todo 1, Response: { ··· }

Received Todo 2, Response: { ··· }

Received Todo 3, Response: { ··· }

Finished!

I particularly like how this method allows the code to remain linear — which is one of the key benefits of using async / await . I find it much easier to read than the alternatives.

If you don’t need access to the index, the code becomes even more concise:

for (const url of urls) { ··· }

One of the major downsides of using a for...of loop is that it performs poorly compared to the other looping options in Javascript. However, the performance argument is negligible when using it to await asynchronous calls, since the intention is to hold up the loop until each call resolves. I typically only use for...of if asynchronous order of execution matters.