Skip to content
Advertisement

JavaScript – Promise.allSettled + Array.reduce()

Introduction

Imagine this method for getting the language of a user:

const getUserLanguage = (userId) => new Promise(
    (resolve, reject) => {
        if (Math.random() < 0.3) resolve("en");
        if (Math.random() < 0.6) resolve("es");
        reject("Unexpected error.");
    }
);


(async () => {
    try {
        const language = await getUserLanguage("Mike")
        console.log(`Language: ${language}`);
    } catch(err) {
        console.error(err);
    }
})();

Now, I am trying to group the language of multiple users, performing a parallel request:

const getUserLanguage = () => new Promise(
    (resolve, reject) => {
        if (Math.random() < 0.3) resolve("en");
        if (Math.random() < 0.6) resolve("es");
        reject("Unexpected error.");
    }
);

const groupUsersByLanguage = async (userIds) => {
    const promiseResults = await Promise.allSettled(
        userIds.reduce(async (acc, userId) => {
            const language = await getUserLanguage(userId);

            (acc[language] = acc[language] ?? []).push(userId);

            return acc;
        }, {})
    );
  
    console.log({ promiseResults });
  
    // Filter fulfilled promises
    const result = promiseResults
        .filter(({ status }) => status === "fulfilled")
        .map(({ value }) => value);
  
    return result;
}

(async () => {
    const userIds = ["Mike", "Walter", "Saul", "Pinkman"];
    const usersGroupedByLanguage = await groupUsersByLanguage(userIds);
    console.log(usersGroupedByLanguage);
})();

Problem

But my implementation is not working:

const promiseResults = await Promise.allSettled(
    userIds.reduce(async (acc, userId) => {
        const language = await getUserLanguage(userId);

        (acc[language] = acc[language] ?? []).push(userId);

        return acc;
    }, {})
);

How can I do for getting an output like

{
    "es": ["Mike", "Saul"],
    "en": ["Walter"],
}

using the Promise.allSettled combined with .reduce?

Advertisement

Answer

Your .reduce is constructing an object where each value is a Promise. Such an object is not something that .allSettled can understand – you must pass it an array.

I’d create an object outside, which gets mutated inside a .map callback. This way, you’ll have an array of Promises that .allSettled can work with, and also have the object in the desired shape.

const getLanguage = () => new Promise(
    (resolve, reject) => {
        if (Math.random() < 0.3) resolve("en");
        if (Math.random() < 0.6) resolve("es");
        reject("Unexpected error.");
    }
);

const groupUsersByLanguage = async (userIds) => {
    const grouped = {};
    await Promise.allSettled(
        userIds.map(async (userId) => {
            const language = await getLanguage(userId);
            (grouped[language] = grouped[language] ?? []).push(userId);
        })
    );
    return grouped;
}

(async () => {
    const userIds = ["Mike", "Walter", "Saul", "Pinkman"];
    const usersGroupedByLanguage = await groupUsersByLanguage(userIds);
    console.log(usersGroupedByLanguage);
})();

An option that doesn’t rely on side-effects inside a .map would be to instead return both the userId and the language inside the map callback, then filter the allSettled results to include only the good ones, then turn it into an object.

const getLanguage = () => new Promise(
    (resolve, reject) => {
        if (Math.random() < 0.3) resolve("en");
        if (Math.random() < 0.6) resolve("es");
        reject("Unexpected error.");
    }
);

const groupUsersByLanguage = async (userIds) => {
    const settledResults = await Promise.allSettled(
        userIds.map(async (userId) => {
            const language = await getLanguage(userId);
            return [userId, language];
        })
    );
    const grouped = {};
    settledResults
        .filter(result => result.status === 'fulfilled')
        .map(result => result.value)
        .forEach(([userId, language]) => {
            (grouped[language] = grouped[language] ?? []).push(userId);
        });
    return grouped;
}

(async () => {
    const userIds = ["Mike", "Walter", "Saul", "Pinkman"];
    const usersGroupedByLanguage = await groupUsersByLanguage(userIds);
    console.log(usersGroupedByLanguage);
})();
Advertisement