Skip to main content

Avoid await in Loops: Clean Async Patterns with Promise.all

Promise.all() vs await in loops.
Last updated: September 6, 2024

During my work with ESLint in projects, I wanted to take full advantage of its rules to ensure my code is well-constructed.

One of the rules I noticed is called no-await-in-loop.

This rule states that you should avoid using the await keyword in loops like for and while.

However, there are situations where we need to make asynchronous calls inside loops, such as deleting order items after deleting an order, or deleting user images after deleting a user from the database, and so on.

For this reason, we should find a way to refactor this pattern.

#The Solution

The solution to this problem is quite simple. To apply it, we will use two concepts in JavaScript:

  1. Promise.all() method from the Promise object
  2. Array.map() method from the Array object

#Hands-On Example

Below is an example of deleting all user images from the user_images table in the database after deleting a user from the users table. We initially used a for-of loop to delete each image from the table:

async function deleteUser(userId: string) {
// Delete the user from the database
await deleteUserFromDB(userId);
// Get user images from the database
const userImages = await getUserImages(userId);
// Loop through each image and delete it
for (const image of userImages) {
await deleteUserImage(image.id);
}
console.log("User and their images have been deleted.");
}

#Refactored Code

Now let’s refactor this code to comply with the ESLint rule:

async function deleteUser(userId: string) {
// Delete the user from the database
await deleteUserFromDB(userId);
// Get user images from the database
const userImages = await getUserImages(userId);
// Use map to create an array of promises without awaiting in the loop
const deleteImagePromises = userImages.map((image) =>
deleteUserImage(image.id),
);
// Execute all promises concurrently
await Promise.all(deleteImagePromises);
console.log("User and their images have been deleted.");
}

In the refactored code:

We extract the loop logic into the map function, where we call the deleteUserImage() method without the await keyword. This returns an array of promises. Afterward, we use the Promise.all() method on the array of promises to execute all asynchronous function calls concurrently.

I hope you enjoyed this tutorial and found it helpful. Following ESLint rules can improve code quality and make it more predictable and clean.