JavaScript - Promise.allSettled + Array.reduce()
JavaScript - Promise.allSettled + Array.reduce()
简介
想象一下这种获取用户语言的方法:
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);
}
})();
现在,我正在尝试对多个用户的语言进行分组,执行并行请求:
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);
})();
问题
但是我的实现不起作用:
const promiseResults = await Promise.allSettled(
userIds.reduce(async (acc, userId) => {
const language = await getUserLanguage(userId);
(acc[language] = acc[language] ?? []).push(userId);
return acc;
}, {})
);
我怎样才能得到像
这样的输出
{
"es": ["Mike", "Saul"],
"en": ["Walter"],
}
使用 Promise.allSettled
结合 .reduce
?
您的 .reduce
正在构建一个对象,其中每个值都是一个 Promise。这样的对象不是 .allSettled
可以理解的东西 - 你必须向它传递一个数组。
我会在外部创建一个对象,它会在 .map
回调中发生变化。这样,您将拥有一个 .allSettled
可以使用的 Promises 数组,并且还具有所需形状的对象。
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);
})();
一个不依赖于 .map
中的 side-effects 的选项是 return 地图回调中的 userId 和语言,然后过滤 allSettled
结果只包括好的结果,然后 把它变成一个对象。
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);
})();
为此,我会使用两个实用函数编写一个主函数:一个根据函数的结果对一组元素进行分组,另一个采用谓词函数并将数组划分为它所针对的那些returns true
和它 returns false
的那些。这两个依次使用 push
效用函数,该函数将 Array.prototype.push
简单地具体化为一个普通函数。
main 函数将 getUserLanguage
函数映射到用户上,调用 Promise.allSettled
结果,然后我们映射生成的承诺,将原始 userId
与承诺结果。 (如果伪造的 getUserLanguage
返回了一个同时具有 userId
和 language
属性的对象,那么这一步就没有必要了。)然后我们划分结果承诺以分离出 fulfilled
来自 rejected
个。我这样做是因为您的问题没有说明如何处理被拒绝的语言查找。我选择在输出中再添加一个条目。这里除了 es
和 en
,我们还在 _errors
下得到了 userId
的列表。如果我们想忽略这些,那么我们可以用 filter
替换 partition
并简化最后一步。最后一步采用成功的结果和失败的结果,将成功的结果与我们的 group
助手组合成一个对象,并通过将失败映射到它们的 userId
来附加 _errors
。
它可能看起来像这样:
// dummy implementation, resolving to random language, or rejecting with error
const getUserLanguage = (userId) => new Promise ((resolve, reject) => {if (Math.random() < 0.3) resolve("en"); if (Math.random() < 0.6) resolve("es"); reject("Unexpected error.");});
// utility functions
const push = (x) => (xs) =>
(xs .push (x), xs)
const partition = (fn) => (xs) =>
xs .reduce (([y, n], x) => fn (x) ? [push (x) (y), n] : [y, push (x) (n)], [[], []])
const group = (getKey, getValue) => (xs) =>
xs .reduce ((a, x, _, __, key = getKey (x)) => ((a [key] = push (getValue (x)) (a[key] ?? [])), a), {})
// main function
const groupUsersByLanguage = (users) => Promise .allSettled (users .map (getUserLanguage))
.then (ps => ps .map ((p, i) => ({...p, user: users [i]})))
.then (partition (p => p .status == 'fulfilled'))
.then (([fulfilled, rejected]) => ({
...group (x => x .value, x => x.user) (fulfilled),
_errors: rejected .map (r => r .user)
}))
// sample data
const users = ['fred', 'wilma', 'betty', 'barney', 'pebbles', 'bambam', 'yogi', 'booboo']
// demo
groupUsersByLanguage (users)
.then (console .log)
.as-console-wrapper {max-height: 100% !important; top: 0}
这会产生这样的输出(YMMV 因为 random
调用):
{
en: [
"fred",
"wilma",
"barney"
],
es: [
"bambam",
"yogi",
"booboo"
],
_errors: [
"betty",
"pebbles"
]
}
请注意,这些效用函数是 general-purpose。如果我们手头有自己的此类工具库,我们可以毫不费力地编写这样的函数。
这样做的另一种选择是首先使用以下方法获取所有语言:
const languages = await Promise.allSettled(userIds.map(getLanguage));
然后与 userIds
一起压缩并进一步处理。
async function getLanguage() {
if (Math.random() < 0.3) return "en";
if (Math.random() < 0.6) return "es";
throw "Unexpected error.";
}
function zip(...arrays) {
if (!arrays[0]) return;
return arrays[0].map((_, i) => arrays.map(array => array[i]));
}
async function groupUsersByLanguage(userIds) {
const languages = await Promise.allSettled(userIds.map(getLanguage));
const groups = {};
for (const [userId, language] of zip(userIds, languages)) {
if (language.status != "fulfilled") continue;
groups[language.value] ||= [];
groups[language.value].push(userId);
}
return groups;
}
(async () => {
const userIds = ["Mike", "Walter", "Saul", "Pinkman"];
const usersGroupedByLanguage = await groupUsersByLanguage(userIds);
console.log(usersGroupedByLanguage);
})();
如果您对创建 zip()
助手不感兴趣,您可以使用“普通”for-loop:
const groups = {};
for (let i = 0; i < userIds.length; i += 1) {
if (languages[i].status != "fulfilled") continue;
groups[languages[i].value] ||= [];
groups[languages[i].value].push(userId);
}
简介
想象一下这种获取用户语言的方法:
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);
}
})();
现在,我正在尝试对多个用户的语言进行分组,执行并行请求:
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);
})();
问题
但是我的实现不起作用:
const promiseResults = await Promise.allSettled(
userIds.reduce(async (acc, userId) => {
const language = await getUserLanguage(userId);
(acc[language] = acc[language] ?? []).push(userId);
return acc;
}, {})
);
我怎样才能得到像
这样的输出{
"es": ["Mike", "Saul"],
"en": ["Walter"],
}
使用 Promise.allSettled
结合 .reduce
?
您的 .reduce
正在构建一个对象,其中每个值都是一个 Promise。这样的对象不是 .allSettled
可以理解的东西 - 你必须向它传递一个数组。
我会在外部创建一个对象,它会在 .map
回调中发生变化。这样,您将拥有一个 .allSettled
可以使用的 Promises 数组,并且还具有所需形状的对象。
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);
})();
一个不依赖于 .map
中的 side-effects 的选项是 return 地图回调中的 userId 和语言,然后过滤 allSettled
结果只包括好的结果,然后 把它变成一个对象。
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);
})();
为此,我会使用两个实用函数编写一个主函数:一个根据函数的结果对一组元素进行分组,另一个采用谓词函数并将数组划分为它所针对的那些returns true
和它 returns false
的那些。这两个依次使用 push
效用函数,该函数将 Array.prototype.push
简单地具体化为一个普通函数。
main 函数将 getUserLanguage
函数映射到用户上,调用 Promise.allSettled
结果,然后我们映射生成的承诺,将原始 userId
与承诺结果。 (如果伪造的 getUserLanguage
返回了一个同时具有 userId
和 language
属性的对象,那么这一步就没有必要了。)然后我们划分结果承诺以分离出 fulfilled
来自 rejected
个。我这样做是因为您的问题没有说明如何处理被拒绝的语言查找。我选择在输出中再添加一个条目。这里除了 es
和 en
,我们还在 _errors
下得到了 userId
的列表。如果我们想忽略这些,那么我们可以用 filter
替换 partition
并简化最后一步。最后一步采用成功的结果和失败的结果,将成功的结果与我们的 group
助手组合成一个对象,并通过将失败映射到它们的 userId
来附加 _errors
。
它可能看起来像这样:
// dummy implementation, resolving to random language, or rejecting with error
const getUserLanguage = (userId) => new Promise ((resolve, reject) => {if (Math.random() < 0.3) resolve("en"); if (Math.random() < 0.6) resolve("es"); reject("Unexpected error.");});
// utility functions
const push = (x) => (xs) =>
(xs .push (x), xs)
const partition = (fn) => (xs) =>
xs .reduce (([y, n], x) => fn (x) ? [push (x) (y), n] : [y, push (x) (n)], [[], []])
const group = (getKey, getValue) => (xs) =>
xs .reduce ((a, x, _, __, key = getKey (x)) => ((a [key] = push (getValue (x)) (a[key] ?? [])), a), {})
// main function
const groupUsersByLanguage = (users) => Promise .allSettled (users .map (getUserLanguage))
.then (ps => ps .map ((p, i) => ({...p, user: users [i]})))
.then (partition (p => p .status == 'fulfilled'))
.then (([fulfilled, rejected]) => ({
...group (x => x .value, x => x.user) (fulfilled),
_errors: rejected .map (r => r .user)
}))
// sample data
const users = ['fred', 'wilma', 'betty', 'barney', 'pebbles', 'bambam', 'yogi', 'booboo']
// demo
groupUsersByLanguage (users)
.then (console .log)
.as-console-wrapper {max-height: 100% !important; top: 0}
这会产生这样的输出(YMMV 因为 random
调用):
{
en: [
"fred",
"wilma",
"barney"
],
es: [
"bambam",
"yogi",
"booboo"
],
_errors: [
"betty",
"pebbles"
]
}
请注意,这些效用函数是 general-purpose。如果我们手头有自己的此类工具库,我们可以毫不费力地编写这样的函数。
这样做的另一种选择是首先使用以下方法获取所有语言:
const languages = await Promise.allSettled(userIds.map(getLanguage));
然后与 userIds
一起压缩并进一步处理。
async function getLanguage() {
if (Math.random() < 0.3) return "en";
if (Math.random() < 0.6) return "es";
throw "Unexpected error.";
}
function zip(...arrays) {
if (!arrays[0]) return;
return arrays[0].map((_, i) => arrays.map(array => array[i]));
}
async function groupUsersByLanguage(userIds) {
const languages = await Promise.allSettled(userIds.map(getLanguage));
const groups = {};
for (const [userId, language] of zip(userIds, languages)) {
if (language.status != "fulfilled") continue;
groups[language.value] ||= [];
groups[language.value].push(userId);
}
return groups;
}
(async () => {
const userIds = ["Mike", "Walter", "Saul", "Pinkman"];
const usersGroupedByLanguage = await groupUsersByLanguage(userIds);
console.log(usersGroupedByLanguage);
})();
如果您对创建 zip()
助手不感兴趣,您可以使用“普通”for-loop:
const groups = {};
for (let i = 0; i < userIds.length; i += 1) {
if (languages[i].status != "fulfilled") continue;
groups[languages[i].value] ||= [];
groups[languages[i].value].push(userId);
}