优雅地处理在`await` JavaScript Promise中的拒绝

25
一个很好的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块中执行函数的其余部分,但这样你会冒着捕获应该在其他地方被忽略的错误的风险。(例如,在编写测试时遇到了这个问题,像测试失败这样的错误需要回退到测试套件,但我想自己处理驱动器实例化。也许我正在落入某种反模式,因为我没有让这些错误回退到测试套件。)
显然,简单的答案是不要在这里使用这种模式。改用Promises和回调函数。或者使用var并避免块作用域的constlet。但对于其他考虑因素,我喜欢重新分配保护和块作用域的好处。
[问题]: 有没有一种方法可以将await/async try/catch块包装到每个函数中? 这似乎是一个潜在的解决方案,但不如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”范围。这是一种不错的类同步处理方式。
每个解决方案都有其权衡之处。
我已经手动提升了变量,并将其放置在函数块的顶部作为工作解决方案(如jmar777的答案中所述),这仍然是一个有趣的问题,因此我现在将保留该问题。

1
async/await 不是 ES7(ES2016)的一部分。它将成为今年发布的 ES2017 的一部分。 - Felix Kling
这里有一个类似的答案(链接:https://dev59.com/O1gR5IYBdhLWcg3ww_ze#49231388),希望能对你有所帮助。 - Cooper Hsiung
2个回答

15
你可能对使用 `const` 的好处存在误解。使用 `const` 声明赋值并不会使该值变为不可变的,它只是意味着该常量的值不能被修改或重新声明:

`const` 声明创建了一个只读引用。这并不意味着它所持有的值是不可变的,它只意味着变量标识符不能被重新分配。例如,在内容为对象的情况下,这意味着对象本身仍然可以被更改。(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/const

在你的示例中,最好将你的 `result` 绑定手动提升到 try/catch 外面:
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 - Tim Hope
你的 try 语句中不需要 return 语句吗? - PositiveGuy
@PositiveGuy 很可能是这样。错误处理逻辑并不是问题的重点,所以这只是一个尝试/捕获存根,以展示手动将 result 提升到更高范围的过程。 - jmar777

3

几年后回到这个问题,我想到了一个更简单的答案。回顾当时我为什么对一个小的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并在finally中返回,则此解决方案将无效。但是我认为我在实际应用中很少遇到这种模式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接