Elegant solution – Nested for loops (eslint no-await-in-loop)

I have some code that works broadly as follows:

function getRegionUsers(region) {
    return new Promise((resolve) => {
        setTimeout(() => {
            if (region === "emea") {
                return resolve(["dave", "mike", "steve"]);
            }
            return resolve(["kiki", "maya", "yukiko"]);
        }, 1000);
    });
}

function sendMail(user, region) {
    console.info(`sendMail called: ${user} (${region})`);
}

async function test() {
    const regions = ["emea", "apac"];
    for (const region of regions) {
        const users = await getRegionUsers(region); // This line is my issue
        for (const user of users) {
            sendMail(user, region);
        }
    }
}
test();

You can ignore the first 2 functions, those just help illustrate the issue. The issue is the 3rd function which causes a linter error (eslint: no-await-in-loop) which makes sense as the await will hold up the execution of each iteration of my for loop.

The solution I keep seeing is to use Promise.all which is fine except I need to keep track of both user and region so I can pass it to the sendMail function. I therefore end up with something like this:

async function test() {
    const regions = ["emea", "apac"];

    const userPromises = regions.map((r) => getRegionUsers(r));
    const regionUsers = await Promise.all(userPromises);

    for (const [i, region] of regions.entries()) {
        for (const user of regionUsers[i]) {
            sendMail(user, region);
        }
    }
}
test();

This seems messy and inelegant since I essentially duplicate the regions for loop and it’s harder to follow. It also gets messier once you deal with error catching or if you have another level or two of nested for loops.

Does anyone have a more elegant solution (other than just telling ESLint to ignore the line) even if it’s just a neater way to write it?

>Solution :

I would map the regions with an async function. The async function always returns a promise (no need to add an explicit return statement). And the promises can be awaited with Promise.all.

async function test() {
    const regions = ["emea", "apac"];

    const promises = regions.map(async region => {
        const users = await getRegionUsers(region);
        for (const user of users) {
            sendMail(user, region);
        }
    });
    await Promise.all(promises);
}

This should fix the problem reported by no-await-in-loop.
It is possible though that other ESLint rules, depending on your configuration, will start to complain.

Leave a Reply