如何在没有“快速失败”行为的情况下并行地等待多个承诺?

我正在使用 async/await并行发出几个 api呼叫:

async function foo(arr) {
const results = await Promise.all(arr.map(v => {
return doAsyncThing(v)
}))
return results
}

我知道,与 loops不同,Promise.all 并行执行(也就是说,等待结果部分是并行的)。

我也知道:

承诺,如果其中一个元素被拒绝,所有元素都被拒绝 承诺。所有的承诺都会很快失败: 如果你有四个承诺,然后解决 一个暂停,一个立即拒绝,然后承诺,所有拒绝 马上。

当我读到这里时,如果我的 Promise.all有5个承诺,而第一个完成的返回一个 reject(),那么其他4个实际上被取消,它们承诺的 resolve()值丢失。

还有第三种方法吗? 在这种方法中,执行有效地并行执行,但一次失败不会破坏整个过程?

119909 次浏览

ES2020包含 一言为定,它可以做你想做的事情。

Promise.allSettled([
Promise.resolve('a'),
Promise.reject('b')
]).then(console.log)

产出:

[
{
"status": "fulfilled",
"value": "a"
},
{
"status": "rejected",
"reason": "b"
}
]

但是如果您想“自己滚动”,那么您可以利用这样一个事实,即使用 Promise#catch意味着承诺解决(除非您抛出来自 catch的异常或者手动拒绝承诺链) ,所以您不需要显式返回已解决的承诺。

因此,通过简单地使用 catch处理错误,您就可以实现您想要的结果。

请注意,如果希望错误在结果中可见,则必须确定显示它们的约定。

您可以使用 数组 # 映射对集合中的每个承诺应用拒绝处理函数,并使用 一言为定等待所有承诺完成。

例子

以下内容应列印出来:

Elapsed Time   Output


0         started...
1s        foo completed
1s        bar completed
2s        bam errored
2s        done [
"foo result",
"bar result",
{
"error": "bam"
}
]

async function foo() {
await new Promise((r)=>setTimeout(r,1000))
console.log('foo completed')
return 'foo result'
}


async function bar() {
await new Promise((r)=>setTimeout(r,1000))
console.log('bar completed')
return 'bar result'
}


async function bam() {
try {
await new Promise((_,reject)=>setTimeout(reject,2000))
} catch {
console.log('bam errored')
throw 'bam'
}
}


function handleRejection(p) {
return p.catch((error)=>({
error
}))
}


function waitForAll(...ps) {
console.log('started...')
return Promise.all(ps.map(handleRejection))
}


waitForAll(foo(), bar(), bam()).then(results=>console.log('done', results))

参见 还有

虽然接受的答案中的技术可以解决您的问题,但它是一种反模式。用一个错误来解决一个承诺不是一个好的实践,有一个更干净的方法来做到这一点。

在伪代码中,您想要做的是:

fn task() {
result-1 = doAsync();
result-n = doAsync();


// handle results together
return handleResults(result-1, ..., result-n)
}

只需使用 async/await就可以做到这一点,而无需使用 Promise.all:

console.clear();


function wait(ms, data) {
return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}


/**
* These will be run in series, because we call
* a function and immediately wait for each result,
* so this will finish in 1s.
*/
async function series() {
return {
result1: await wait(500, 'seriesTask1'),
result2: await wait(500, 'seriesTask2'),
}
}


/**
* While here we call the functions first,
* then wait for the result later, so
* this will finish in 500ms.
*/
async function parallel() {
const task1 = wait(500, 'parallelTask1');
const task2 = wait(500, 'parallelTask2');


return {
result1: await task1,
result2: await task2,
}
}


async function taskRunner(fn, label) {
const startTime = performance.now();
console.log(`Task ${label} starting...`);
let result = await fn();
console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } miliseconds with,`, result);
}


void taskRunner(series, 'series');
void taskRunner(parallel, 'parallel');

注意: 您需要一个启用 async/await的浏览器来运行这个代码片段。

这样,您可以简单地使用 try/catch来处理错误,并在 parallel函数中返回部分结果。