JavaScript Promises in For Loops

JavaScript promises in For Loop

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 reference, here is the wrong approach you were probably thinking of:

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

To learn more about what problem async/await solves and how it works, please stick around.

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 can cause unexpected behavior.

For example, you may end up printing values that are not available yet.

The Solution—Async/Await

To solve this problem, your loop needs to await for the actions of each round to complete before moving on to the next one.

To do this, you need to mark your function async, and place await keyword in front of the action you want to wait for.

Example—Sending Emails

Say we are sending out a bunch of emails.

Sending an email is an asynchronous process that takes 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 sent, the system notifies us.

To simulate this, let’s create:

  • An array of email addresses.
  • A function that simulates sending an email. It does this by resolving a promise after a second of delay.
  • A function that loops through the array of email addresses and sends each email. This function notifies us when a mail is sent.

Here is the code:

const emails = ['alice@gmail.com', 'bob@gmail.com', 'charlie@gmail.com'];

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();

But here is the problem. Calling sendAllEmails() results in an unexpected output:

Mail sent to [object Promise]
All emails were sent

It immediately prints out both of the messages seen above.

The expected behavior would be to see the notifications between one-second intervals like this:

Mail sent to alice@gmail.com
Mail sent to bob@gmail.com
Mail sent to charlie@gmail.com
All emails were sent

So, what went wrong?

The problem is the code runs synchronously, while the actions are asynchronous.

The for loop does not wait for the messages to be sent.

To change this, we need to:

  • Make the sendAllEmails() function asynchronous.
  • Wait for the promise returned by the send() function to resolve.

To do this:

  • Mark the sendAllEmails() function async.
  • Wait for the send() to complete using await keyword.

Here is the code after changes:

const emails = ['alice@gmail.com', 'bob@gmail.com', 'charlie@gmail.com'];

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 alice@gmail.com
Mail sent to bob@gmail.com
Mail sent to charlie@gmail.com
All emails were sent

Conclusion

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

Thanks for reading. I hope you find it useful.

Happy coding!

Further Reading

100 JavaScript Interview Questions

50 Buzzwords of Web Development

Share on facebook
Facebook
Share on google
Google+
Share on twitter
Twitter
Share on linkedin
LinkedIn
Share on pinterest
Pinterest