Await in for loop javascript

I came across a complaint on how the forEach loop is inconsistent when you pass in an asynchronous lambda function as the callback argument to be run for every element. On the surface, it doesn’t seem like an unrealistic expectation but in this article, I will try to explain why it doesn’t work, show you a popular way developers implement this, and finally an optimization that works for a specific scenario (this is usually the scenario most people need this for).

Quick background (async/await)

If you already have a firm grasp on async/await you can skip this section. Something else I have noticed is, kinda like me most people do not understand javascript promises and because of that, they do not understand what the async-await syntax is actually doing. The async-await syntax is just syntactic sugar on top of the promises API, the async tag on a function simply lets javascript know that this function would return a promise and the awaits inside the functions tell the interpreter to stay on this line of code inside this function call till the promise called on that line is fully resolved. It makes the ugly nested .then() calls look a lot more readable.

The problem (Promises)

The forEach loop was not built to work with asynchronous callback functions which is the reason it does not do what you may be expecting. It does not wait for the promise of an iteration to be resolved before it goes on to the next iteration. This means that by the end of the forEach loop’s iterations nothing has actually been pushed into the users’ array (it gets pushed a little later but I do not really want to get into the event loop here).

Looking closely at the problem you would see that the main reason our code does not work correctly is that we are trying to use the forEach loop for something it was not created for. We need something that lets us iterate over an array and allows us to block the program execution till our promise is resolved and that is where the base for loop comes in.

We could use either the for…of the loop or the old for(let i = 0;….) type of for loop but I will use for…of here because we don’t need that much control over the iteration process.

Nore: The for…of the loop has to be inside an async function to use await inside the loop. This syntax cannot be used at the top level of your program unless you have set some extra config on your running environment.

This would work exactly as you expect and the resolved users would be logged.

Optimization (we can do a little better can’t we)

Looking at our solution from the previous section, you can see some problems that can arise. Imagine it takes 5 seconds to get the user on the getUser call, that would mean it would take 25 seconds to log all the users and this is even worse when you look at this problem at a larger scale.

For this particular scenario of getting users from an array of user ids, you may notice that we are waiting for one user to be returned before we begin fetching the next user but we do not need to wait because fetching the next user has no dependency on the value returned from the previous call. Can we fetch them all in parallel so it’ll take 5 seconds to get them all instead? yeah, definitely. This is where Promise.all() comes in, we use it to batch the resolution of the promises.

Using Promise.all(), all promises are being resolved in parallel it’ll take the same time it takes to resolve one to resolve all of them. One important thing to note is that you get access to all resolved promises only when all the promises are resolved so if one of the calls takes 10 seconds while the rest takes 2 seconds, you will have to wait 10 seconds.

Conclusion

I’m not really a writer so I hope you understood this and I’m done. Also, if you have any better ways to do this please let me know in the comments, I like learning new things too.

To use Javascript promises in a for loop, use async/await.

Here is a code skeleton for the correct approach:

async function doSomething() {
    for (item of items) {
        await promiseAction(item)
    }
}

This waits for each promiseAction to complete before continuing to the next iteration in the loop.

For comparison, here is the incorrect approach you were probably thinking of:

function doSomething() {
    for (item of items) {
       promiseAction(item)
    }
}

In this guide, you learn how async/await works and how it solves the problem of using promises in for loops.

The Problem—For Loops Don’t Wait for Async Actions

When you are using promises in a for loop, the loop does not wait for the promises to resolve/reject by default.

This causes unexpected behavior. For example, you may end up trying to access values that are not available yet.

To solve this problem, you need to make the loop wait for the asynchronous actions to complete. This is possible using async/await.

The Solution—Async/Await

Your loop needs to wait for the asynchronous task to complete on each round of the for loop.

To make this happen, mark your function asynchronous using the async keyword, and place an await keyword in front of the action you want to wait for.

Let’s see an example where we first try to run asynchronous code as if it was synchronous. Then, let’s fix the issue by using async/await.

Example—Sending Emails

Say we are sending out a bunch of emails.

Sending an email over the internet is an asynchronous task that takes some unspecified time to complete.

In this example, we build a notification system, that notifies us when an email is sent. Also, when all the emails are successfully sent, the system notifies us once more.

Mail sent to [email protected]
Mail sent to [email protected]
Mail sent to [email protected]
All emails were sent

To simulate this, let’s create:

  • An array of email addresses.
  • A function that simulates sending an email by resolving a promise after one second delay.
  • A function that loops through the array of email addresses, sends each email, and notifies us.

Here is an incorrect approach that does not take into account the asynchronous nature of this task:

const emails = ['[email protected]', '[email protected]', '[email protected]'];

const send = email =>
  new Promise(resolve =>
    setTimeout(() => resolve(email), 1000)
  );

const sendAllEmails = () => {
  for (email of emails) {
    const emailInfo = send(email);
    console.log(`Mail sent to ${emailInfo}`);
  }

  console.log('All emails were sent');
};

sendAllEmails();

Running this piece of code results in an unexpected output:

Mail sent to [object Promise]
All emails were sent

It immediately prints out these notifications to the console without waiting.

So, what went wrong?

The problem is our code runs synchronously, while the tasks are asynchronous. The for loop does not wait for the messages to be sent. Instead, it sends each mail immediately.

So you need to make the for loop wait for each message to be sent.

To do this:

  • Mark the email sending function asynchronous.
  • Await for each send action to complete.

To turn this idea into code:

  • Mark the sendAllEmails() function async. This tells JavaScript that the function is an asynchronous.
  • Wait for the send() function to complete using await keyword.

Here is the updated code:

const emails = ['[email protected]', '[email protected]', '[email protected]'];

const send = email =>
  new Promise(resolve =>
    setTimeout(() => resolve(email), 1000)
  );

const sendAllEmails = async () => {
  for (email of emails) {
    const emailInfo = await send(email);
    console.log(`Mail sent to ${emailInfo}`);
  }

  console.log('All emails were sent');
};

sendAllEmails();

Now, this results in the desired output:

Mail sent to [email protected]
Mail sent to [email protected]
Mail sent to [email protected]
All emails were sent

This completes our guide on promises in for loops.

Conclusion

Today you learned how to use async/await to make Javascript promises work in a for loop.

Never heard about async/await before? I recommend reading Understanding async/await in JavaScript.

To recap, mark the asynchronous function async, and wait for the asynchronous action to complete with await.

Thanks for reading. I hope you find it useful.

Happy coding!

Further Reading

100 JavaScript Interview Questions

50 Buzzwords of Web Development

Can we use await in for loop JavaScript?

You need to place the loop in an async function, then you can use await and the loop stops the iteration until the promise we're awaiting resolves. You could also use while or do.. while or for loops too with this same structure.

Does for loop wait for await?

Using await inside a for loop will cause the code to stop and wait for the asynchronous operation to complete before continuing. This means that all promises will be run sequentially.

How do you wait for loop to finish?

To use Javascript promises in a for loop, use async / await . This waits for each promiseAction to complete before continuing to the next iteration in the loop.

What happens if you use await inside a loop and what are the alternatives?

When evaluate for loop, we have await promise inside the async function, the execution will pause until the await promise is settled. So, you can think of that the files are read one by one in a determined order. Sometimes, we really need the the async functions to be executed in a sequential order.