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