如何在没有“快速失败”行为的情况下并行等待多个承诺? [复制]
Posted
技术标签:
【中文标题】如何在没有“快速失败”行为的情况下并行等待多个承诺? [复制]【英文标题】:How do I await multiple promises in-parallel without 'fail-fast' behavior? [duplicate] 【发布时间】:2017-05-08 14:53:39 【问题描述】:我正在使用async
/await
并行触发多个api
调用:
async function foo(arr)
const results = await Promise.all(arr.map(v =>
return doAsyncThing(v)
))
return results
我知道,与loops
、Promise.all
executes in-parallel 不同(也就是说,等待结果部分是并行的)。
但是I also know that:
如果其中一个元素被拒绝并且 Promise.all 被拒绝并且 Promise.all 快速失败:如果您有四个承诺在之后解决 超时,一个立即拒绝,然后 Promise.all 拒绝 马上。
当我读到这篇文章时,如果我 Promise.all
有 5 个承诺,而第一个完成的承诺返回 reject()
,那么其他 4 个将被有效地取消,并且它们承诺的 resolve()
值将丢失。
还有第三种方法吗?哪里的执行实际上是并行的,但一个单一的失败不会破坏整个过程?
【问题讨论】:
其他四个没有被取消,但是它们的结果没有沿着承诺链(IIUC)传播。 如果你想避免特定的失败模式拒绝承诺链,那么你可以在子承诺链中处理该失败(使用catch
),从而避免快速失败。这会做你想做的事吗?
const noop = function()
Promise.all( arr.map( v => doAsyncThing(v).catch(noop) ) )
将错误转换为未定义值
@BenAston 你的意思是不是 return doAsyncThing(v)
, return doAsyncThing(v).catch(err => return err)
? ETA:我不确定catch
正文中应该包含什么——有没有办法在没有reject
的情况下传递错误对象?只要我可以单独处理reject()
ed 承诺并且仍然从resolve()
d 承诺中获取值。回复:cancelled
与 not propagated
,我不确定我是否理解其中的区别。两者都导致丢失 resolve()
值,是吗?
@Thomas 会将 reject()
ed 的承诺最终在结果 array
中作为 undefined
?
【参考方案1】:
虽然已接受答案中的技术可以解决您的问题,但它是一种反模式。解决带有错误的 Promise 并不是一个好的做法,并且有一种更简洁的方法。
你想用伪代码做的是:
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
的浏览器来运行这个sn-p。
这样你可以简单地使用try
/catch
来处理你的错误,并在parallel
函数中返回部分结果。
【讨论】:
被低估了。这很清楚到底发生了什么。 “反模式:用错误解决承诺不是好的做法” - 你从哪里得到的?不,相反,yourparallel
function is an antipattern 可能会导致未处理的拒绝。
更糟糕的是:这甚至不能解决 OPs 问题。如果 task1
在 task2
完成之前拒绝,你仍然很快失败。
你从哪里得到的? - 处理 then
中的错误是反模式,因为 Promises 有一种专门的方法来处理 catch
分支中的错误,所以开发人员期望 then
中的值和 catch
中的错误。将其混为一谈会导致其他不了解您的自定义实施细节的人感到困惑。
您的并行函数是一种反模式 - 如果您阅读了我的回答,您可以在底部找到如何处理:“这种方式您可以简单地使用try/catch 来处理您的错误,并在并行函数中返回部分结果。” 这也是您第二条评论的答案:更糟糕的是:这甚至不能解决 OPs 问题。 【参考方案2】:
ES2020 包含Promise.allSettled,它会做你想做的事。
Promise.allSettled([
Promise.resolve('a'),
Promise.reject('b')
]).then(console.log)
输出:
[
"status": "fulfilled",
"value": "a"
,
"status": "rejected",
"reason": "b"
]
但是如果你想“自己动手”,那么你可以利用这样一个事实,即使用Promise#catch
意味着承诺解决(除非你从catch
抛出异常或手动拒绝承诺链),所以您不需要显式返回已解决的承诺。
因此,只需使用catch
处理错误,您就可以实现您想要的。
请注意,如果您希望错误在结果中可见,则必须确定显示它们的约定。
您可以使用Array#map 对集合中的每个promise 应用拒绝处理函数,并使用Promise.all 等待它们全部完成。
示例
应打印出以下内容:
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))
见also。
【讨论】:
return await 是多余的 fyi eslint.org/docs/rules/no-return-await 当你已经使用 await 时,为什么还要使用 .then?您可以删除所有 .then 调用,它会起作用。 你的脚本的输出不包括'bar result'和'bat result',但是:done [ undefined, "error": "bam" , undefined ] 是这样的吗?跨度> 是的,我相信它应该return new Promise
这样我们才能得到正确的结果数组:jsbin.com/ruralujame/edit?html,css,js,console,output
添加到 David 的评论中,返回了 done [ undefined, "error": "bam" , undefined ]。如果添加 return await 和 then for bam,则显示结果 [Log] done – ["bar result", "bam result", "bat result"]。但是我无法完成结果 - ["bar result", "error":"bam", "bat result"]。有没有办法做到这一点?以上是关于如何在没有“快速失败”行为的情况下并行等待多个承诺? [复制]的主要内容,如果未能解决你的问题,请参考以下文章