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

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

我知道,与loopsPromise.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 承诺中获取值。回复:cancellednot 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函数中返回部分结果。

【讨论】:

被低估了。这很清楚到底发生了什么。 反模式:用错误解决承诺不是好的做法” - 你从哪里得到的?不,相反,your parallel function is an antipattern 可能会导致未处理的拒绝。 更糟糕的是:这甚至不能解决 OPs 问题。如果 task1task2 完成之前拒绝,你仍然很快失败。 你从哪里得到的? - 处理 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"]。有没有办法做到这一点?

以上是关于如何在没有“快速失败”行为的情况下并行等待多个承诺? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

XState:在没有中间状态的情况下链接多个 Promise

Javascript,如何等待多个承诺[重复]

等待多个承诺完成

并行解决承诺

等待所有承诺解决

等待所有承诺解决