• 如何优雅地处理Async/Await的异常?


    如何优雅地处理Async/Await的异常?

    译者按: 使用.catch()来捕获所有的异常

    本文采用意译,版权归原作者所有

    async/await 中的异常处理很让人混乱。尽管有很多种方式来应对async 函数的异常,但是连经验丰富的开发者有时候也会搞错。

    假设你有一个叫做run()的异步函数。在本文中,我会描述 3 种方式来处理run()的异常情形: try/catchGo 语言风格, 函数调用的时候使用 catch()(即run().catch())。 我会跟你解释为什么其实几乎只需要catch()就足够。

    try/catch

    当你第一次使用async/await, 你可能尝试使用try/catch将每一个 async 操作包围起来。如果你await一个被 reject 的 Promise,JavaScript 会抛出一个可以被捕获的错误。

    1.  
      run();
    2.  
       
    3.  
      async function run() {
    4.  
      try {
    5.  
      await Promise.reject(new Error("Oops!"));
    6.  
      } catch (error) {
    7.  
      error.message; // "Oops!"
    8.  
      }
    9.  
      }

    try/catch 能够捕获非异步的异常。

    1.  
      run();
    2.  
       
    3.  
      async function run() {
    4.  
      const v = null;
    5.  
      try {
    6.  
      await Promise.resolve("foo");
    7.  
      v.thisWillThrow;
    8.  
      } catch (error) {
    9.  
      // "TypeError: Cannot read property 'thisWillThrow' of null"
    10.  
      error.message;
    11.  
      }
    12.  
      }

    所以,只需要将所有的代码逻辑都用 try/catch包围起来就可以搞定?也不完全正确。下面的代码会抛出unhandled promise rejectionawait将一个被拒绝的 promise 转换为可捕获的错误,但是 return 不行。

    1.  
      run();
    2.  
       
    3.  
      async function run() {
    4.  
      try {
    5.  
      // 注意这里是return,不是await
    6.  
      return Promise.reject(new Error("Oops!"));
    7.  
      } catch (error) {
    8.  
      // 代码不会执行到这里
    9.  
      }
    10.  
      }

    也不可能使用 return await来绕开。

    还有一个缺点就是使用了try/catch 之后,就很难用.的语法来进行 Promise 链式组合了。

    使用 Go 的语法

    另一个常见的方式就是使用then()将一个本来需要用catch()来捕获并处理的 Promise 转换为普通的 Promise。然后像 Go 语言中一样,使用if(err)来处理异常。

    1.  
      run();
    2.  
       
    3.  
      async function throwAnError() {
    4.  
      throw new Error("Oops!");
    5.  
      }
    6.  
       
    7.  
      async function noError() {
    8.  
      return 42;
    9.  
      }
    10.  
       
    11.  
      async function run() {
    12.  
      // The `.then(() => null, err => err)` 来匹配正常/异常的情况。如果正常情况,返回`null`;如果异常,返回`err`
    13.  
      let err = await throwAnError().then(() => null, err => err);
    14.  
      if (err != null) {
    15.  
      err.message; // 'Oops'
    16.  
      }
    17.  
       
    18.  
      err = await noError().then(() => null, err => err);
    19.  
      err; // null
    20.  
      }

    如果你真的想要同时返回 error 和正确的值,你可以完全假装在用 Go 语言。

    1.  
      run();
    2.  
       
    3.  
      async function throwAnError() {
    4.  
      throw new Error("Oops!");
    5.  
      }
    6.  
       
    7.  
      async function noError() {
    8.  
      return 42;
    9.  
      }
    10.  
       
    11.  
      async function run() {
    12.  
      // The `.then(v => [null, v], err => [err, null])` pattern
    13.  
      // 你可以使用数组解构来匹配err和返回值
    14.  
      let [err, res] = await throwAnError().then(
    15.  
      v => [null, v],
    16.  
      err => [err, null]
    17.  
      );
    18.  
      if (err != null) {
    19.  
      err.message; // 'Oops'
    20.  
      }
    21.  
       
    22.  
      err = await noError().then(v => [null, v], err => [err, null]);
    23.  
      err; // null
    24.  
      res; // 42
    25.  
      }

    使用 Go 语言风格的错误处理并不能摆脱return无法捕获的情况。而且还让整个代码更加的复杂,如果忘记if(err != null),就会出问题。

    总的来说,有两大缺点:

    1. 代码极度重复,每一个地方都少不了if (err != null) ,真的很累,而且容易漏掉;
    2. run()函数中的非异步的错误也无法处理;

    总的来说,它并没有比try/catch好多少。

    在函数调用的时候使用catch()

    try/catch 和 Go 语言风格的异常处理都有各自的使用场景,但是处理所有异常最好的方法是在run()函数的后面使用catch(),像这样:run().catch()。换句话说,用一个catch()来处理run函数中的所有错误,而不是针对run里面的每一种情况都去写代码做相应的处理。

    1.  
      run()
    2.  
      .catch(function handleError(err) {
    3.  
      err.message; // Oops!
    4.  
      })
    5.  
      // 在handleError中处理所有的异常
    6.  
      // 如果handleError出错,则退出。
    7.  
      .catch(err => {
    8.  
      process.nextTick(() => {
    9.  
      throw err;
    10.  
      });
    11.  
      });
    12.  
       
    13.  
      async function run() {
    14.  
      await Promise.reject(new Error("Oops!"));
    15.  
      }

    记住,async 函数总是返回 promise。只要函数中有异常,Promise 会 reject。而且,如果一个 async 函数返回的是一个 reject 的 Promise,那么这个 Promise 依然会继续被 reject。

    1.  
      run()
    2.  
      .catch(function handleError(err) {
    3.  
      err.message; // Oops!
    4.  
      })
    5.  
      .catch(err => {
    6.  
      process.nextTick(() => {
    7.  
      throw err;
    8.  
      });
    9.  
      });
    10.  
       
    11.  
      async function run() {
    12.  
      // 注意:这里使用了return,而不是await
    13.  
      return Promise.reject(new Error("Oops!"));
    14.  
      }

    为什么使用run().catch()而不是将整个run()函数用try/catch包起来呢?我们首先来考虑一个情况:如果try/catchcatch部分有异常,我们应该如何处理呢?只有一个方法:在catch里面接着使用try/catch。所以,run().catch()的模式使得异常处理变得非常简洁。

    总结

    我们最好是全局的有一个 errorHandler 来处理那些没有考虑到的异常,比如使用run().catch(handleError),而不是在run()函数里面所有可能出错的地方加上try/catch

  • 相关阅读:
    eBay要怎么转型?
    一张图让你看懂javascript各类型的关系
    闭包概念,匿名函数
    浅析Javascript闭包的特性
    深入理解JavaScript闭包(closure)
    深入理解JavaScript作用域和作用域链
    WPF 学习笔记(一)
    ChromiumWebBrowser flash不能自动播放问题解决方案
    饱含辛酸开发 WPF CustomControl
    KMP算法备忘
  • 原文地址:https://www.cnblogs.com/mouseleo/p/13611923.html
Copyright © 2020-2023  润新知