优雅地处理 `await`ed Javascript Promise 上的拒绝

Posted

技术标签:

【中文标题】优雅地处理 `await`ed Javascript Promise 上的拒绝【英文标题】:Elegantly Handling Reject on `await`ed Javascript Promise 【发布时间】:2017-06-02 08:58:50 【问题描述】:

ES2017 async/await 的一个很好的模式是:

async function () 
  try 
    var result = await some_promised_value()
   catch (err) 
    console.log(`This block would be processed in
      a reject() callback with promise patterns
      but this is far more intuitive`)
    return false // or something less obtuse
  
  result = do_something_to_result(result)
  return result;

能够处理这样的错误真是太好了。但是假设我想异步获取一个值,我想保护它不被重新分配(例如:数据库会话),但我仍然想使用 async/await 模式(纯粹是因为我认为它更直观)。

以下内容不起作用,因为 const 是块作用域:

async function () 
  try 
    const result = await get_session()
   catch (err) 
    console.log(`This block should catch any
      instantiation errors.`)
    return false
  
  // Can't get at result here because it is block scoped.

当然,您可以在 try 块中执行其余的函数,但是您可能会遇到错误,而这些错误应该会在其他地方漏掉。 (例如,我在编写测试时遇到了这个挑战,其中像测试失败这样的错误需要退回到测试套件,但我想自己处理驱动程序实例化。也许我在这里陷入了某种反模式,不让这些错误退回到测试套件。)

简单的答案显然是不要在这里使用那种模式。改用 Promise 和回调。或者使用var 并避免块作用域constlet。但我喜欢重新分配保护和阻止范围的好处。

[问题]:Is there a way to wrap an await/async try/catch block to every function? 似乎是一种潜在的解决方案,但不像try/catch 那样可读。 try/catch 不会破坏函数范围并且我可以在 try/catch 块内使用 return 这一事实在我看来更符合 async/await 的精神,为异步带来了更具程序性的逻辑代码。

理想情况下,您希望执行const x = await y() catch (err)const x = await y() || fail 之类的操作,但我想不出任何语法正确的类似流程。

更新: 正如@jmar777 所建议的,下面的另一种选择是:

const x = await y().catch(() => /*handle errors here*/)

这可能是我在实践中发现的与最后两个示例最接近的。但它打破了上面示例中阻止下游执行的return 范围。这是一种很好的类似同步的处理方式。

每个解决方案都有其权衡。

我已经在功能块顶部手动提升了带有let 的变量作为一个工作解决方案(如 jmar777 的回答中所述)仍然是一个有趣的问题,可以看到各种方法,所以我会留下这个问题暂时。

【问题讨论】:

async/await 不是 ES7 (ES2016) 的一部分。它将成为今年发布的 ES2017 的一部分。 一个类似的答案here希望能帮到你。 另见syntax to handle rejections with async/await 【参考方案1】:

您可能对使用const 的好处有误解。使用 const 声明分配一个值不会使该值不可变,它只是意味着该常量的值不能更改或重新声明:

const 声明创建一个对值的只读引用。这并不意味着它持有的值是不可变的,只是不能重新分配变量标识符。例如,如果内容是对象,这意味着对象本身仍然可以更改。 (https://developer.mozilla.org/en-US/docs/Web/javascript/Reference/Statements/const)

在您的示例中,您最好手动在 try/catch 之外提升 result 绑定:

async function () 
  let result;

  try 
    result = await get_session()
   catch (err) 
    console.log(`This block should catch any
      instantiation errors.`)
  

  // do whatever else you need with result here...

另一种方法是重构代码,使您根本不需要依赖 try/catch。例如:

async function () 
  const result = await get_session().catch(someRejectionHandler); 

  // do whatever else you need with result here...

请注意,您的下游代码需要优雅地处理get_session() 被拒绝以及result 未根据成功响应进行初始化的情况。这与您最初的示例没有什么不同,但在扫描代码时可能不那么明显。

【讨论】:

啊,我想我误解了“不可变”的语义,而不是 const 的含义。我将编辑问题以消除混淆。同样在我编写的原始代码中,我在catch 块中有一个return 以防止下游执行,我将更新伪代码以匹配。从catch 内部返回的能力是我更喜欢带有回调的.catch 的原因之一,它似乎在风格上更符合async/await。您的吊装解决方案是我最终使代码正常工作的解决方案。不过还是有 const 就好了。 您的尝试中不需要返回声明吗? @PositiveGuy 最有可能。错误处理逻辑不是问题的主题,所以这实际上只是一个 try/catch 存根,以演示手动将 result 提升到更高范围。【参考方案2】:

几年后回到这个问题,我想到了一个简单得多的答案,回想起来我不明白为什么我对一个小的 try/catch 对感兴趣,然后在我可以的时候在它之外执行更多的逻辑只需执行所有逻辑并在 try 语句中返回。

以问题为例并应用该结构:

 async function () 
  try 
    const result = await get_session()
    // Do what needs to be done and then:
    return result
   catch (err) 
    console.log(`This block should catch any
      instantiation errors.`)
    return false
  

如果您需要从 catch 块中的 try 块访问任何内容,请将其引发错误。当我问这个问题时,可能有一个原因我需要离开 try 范围,但我看不到需要回顾的场景。如果您要使用 try/catch/finally 并最终返回,则此解决方案将不起作用。但我认为我在野外根本没有遇到过这种模式。

【讨论】:

以上是关于优雅地处理 `await`ed Javascript Promise 上的拒绝的主要内容,如果未能解决你的问题,请参考以下文章

Js不使用try catch,优雅地处理await报错的情况

优雅地 `async/await`

javascript中优雅的处理async和await异常

如何优雅处理 async await 错误——解读小而美的 awaitjs 库

如何优雅处理 async await 错误——解读小而美的 await-to-js 库

Callback Promise Generator Async-Await 和异常处理的演进