当前接受的答案建议使用Promise.all() 而不是async reduce。但是,这与 async reduce 的行为不同,并且仅适用于您希望异常立即停止所有迭代的情况,但情况并非总是如此。
此外,在该答案的 cmets 中,建议您始终将累加器作为减速器中的第一个语句等待,否则您可能会面临未处理的承诺拒绝的风险。海报还说这是 OP 所要求的,但事实并非如此。相反,他只想知道一切何时完成。为了知道你确实需要做await acc,但这可能在减速器的任何时候。
const reducer = async(acc, key) => {
const response = await api(item);
return {
...await acc, // <-- this would work just as well for OP
[key]: reponse,
}
}
const result = await ['a', 'b', 'c', 'd'].reduce(reducer, {});
console.log(result); // <-- Will be the final result
如何安全使用asyncreduce
话虽如此,以这种方式使用减速器确实意味着您需要保证它不会抛出,否则您将得到“未处理的承诺拒绝”。完全有可能通过使用try-catch 来确保这一点,catch 块返回累加器(可选地带有失败的 API 调用的记录)。
const reducer = async (acc, key) => {
try {
data = await doSlowTask(key);
return {...await acc, [key]: data};
} catch (error) {
return {...await acc, [key]: {error}};
};
}
const result = await ['a', 'b', 'c','d'].reduce(reducer, {});
与Promise.allSettled的区别
您可以使用Promise.allSettled 接近async reduce(带有错误捕获)的行为。然而,这使用起来很笨拙:如果你想归约到一个对象,你需要在它之后添加另一个同步归约。
Promise.allSettled + 常规reduce 的理论时间复杂度也更高,尽管可能很少有用例会产生影响。 async reduce 可以从第一个项目完成的那一刻开始累积,而在 Promise.allSettled 之后的 reduce 被阻止,直到所有的承诺都被履行。在循环大量元素时,这可能会有所不同。
const responseTime = 200; //ms
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const api = async (key) => {
console.log(`Calling API for ${ key }`);
// Boz is a slow endpoint.
await sleep(key === 'boz' ? 800 : responseTime);
console.log(`Got response for ${ key }`);
if (key === 'bar') throw new Error(`It doesn't work for ${ key }`);
return {
[key]: `API says ${ key }`,
};
};
const keys = ['foo', 'bar', 'baz', 'buz', 'boz'];
const reducer = async (acc, key) => {
let data;
try {
const response = await api(key);
data = {
apiData: response
};
} catch (e) {
data = {
error: e.message
};
}
// OP doesn't care how this works, he only wants to know when the whole thing is ready.
const previous = await acc;
console.log(`Got previous for ${ key }`);
return {
...previous,
[key]: {
...data
},
};
};
(async () => {
const start = performance.now();
const result = await keys.reduce(reducer, {});
console.log(`After ${ performance.now() - start }ms`, result); // <-- OP wants to execute things when it's ready.
})();
用Promise.allSettled检查执行顺序:
const responseTime = 200; //ms
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const api = async (key) => {
console.log(`Calling API for ${ key }`);
// Boz is a slow endpoint.
await sleep(key === 'boz' ? 800 : responseTime);
console.log(`Got response for ${ key }`);
if (key === 'bar') throw new Error(`It doesn't work for ${ key }`);
return {
key,
data: `API says ${ key }`,
};
};
const keys = ['foo', 'bar', 'baz', 'buz', 'boz'];
(async () => {
const start = performance.now();
const apiResponses = await Promise.allSettled(keys.map(api));
const result = apiResponses.reduce((acc, {status, reason, value}) => {
const {key, data} = value || {};
console.log(`Got previous for ${ key }`);
return {
...acc,
[key]: status === 'fulfilled' ? {apiData: data} : {error: reason.message},
};
}, {});
console.log(`After ${ performance.now() - start }ms`, result); // <-- OP wants to execute things when it's ready.
})();