Using Async/Await in a Loop
The bane of a developer’s existence is looping, and now you want to perform async/await calls INSIDE one? No way!
I’m here to tell you that there is a way, and it’s actually pretty easy.
First, let’s talk a little about asynchronous calls. Asynchronous calls are used primarily when you are waiting for a response from a backend service layer. There are many useful tutorials out there going over how async/await works and what the best practices may be, so I won’t bore you with those details today.
A for-loop is meant to traverse an array or list of objects. If you want to call an API inside of that for-loop (for example, you’re looping through a list of users and want to use their unique ID to find profile information for each user), the loop will not necessarily wait for your promise to finish executing before it moves on. This will almost always end up being a problem.
There are three (3) key pieces to setting up async/await in your for-loop: the actual loop, the async function, and Promise.all().
The for-loop. This looks like most typical for-loops. Nothing new here!
for(let i = 0; i < users.length; i ++) {
// where we'll set up our array of promises
}
Like mentioned earlier, the for-loop will not wait for your asynchronous call to await (or complete). Because of this, we need to store the async call in an array to use later.
Update the above code to look like this:
const promises = []; // new linefor (let i = 0; i < users.length; i ++) {
promises.push(this.getProfile(users[i])) // new line
}
In this code, we’re looping through users
and passing our asynchronous function users[i]
(which is the user our loop is currently evaluating). We are then pushing the asynchronous call to promises
so we can execute them later.
The async function. Our function from earlier getProfile()
would typically interact with the backend and need to await
a response. Here is an example of a TypeORM/NestJS service that you may need to await:
public async getProfile(user) {
return await this.repository.findOne({ _id: user._id })}
In the above example, we are using TypeORM/NestJS to query a database. This article is assuming you have some background and familiarity with writing services. This function is fairly universal in that this.repository.findOne(...)
can be replaced with any API call (frontend) or database query (backend) that you would normally need to await
due to it being asynchronous.
The asynchronous call gets returned from getProfile
and is pushed to the promises
array, which is basically just storing and tracking what promises need to be resolved later.
Resolve the promises. To resolve an array of promises in Javascript, we use a line called Promise.all()
. Promise.all() loops through your array of promises and executes them.
const promises = [];for (let i = 0; i < users.length; i ++) {
promises.push(this.getProfile(users[i]))
}const userProfiles = await Promise.all(promises); // new line
This solution is not perfect — you may want to use Promise.allSettled()
if there is concern around some promises failing. When using Promise.all()
, if one promise fails then the entire execution will fail. There is more on that here.
Being able to perform asynchronous calls within a loop has become so important to some of my projects that I’ve created a “portable” function that I can copy/paste into a new project as needed.
Data — the array of data that I’m looping through.
Self — and instance of this
for my passedFunction to reference.
PassedFunction — the asynchronous call I’m trying to make.
export const setupAsyncPromises = (data, self, passedFunction) => {
const promises = [];
for (let i = 0; i < data.length; i ++) {
promises.push(passedFunction(data[i], self));
} return Promise.all(promises)
}// IMPLEMENTATION of my exported/portable function.// in my services fileconst self = this;
const userProfiles = await setupAsyncPromises(users, self, self.getUserProfiles)// the async function public async getUserProfiles(user, self) {
return await self.profileRepository.findOne({ _id: user._id });
}
Now you know how to complete asynchronous calls inside a loop!