95

你真的会在async/await中捕获异常吗?

 6 years ago
source link: https://segmentfault.com/a/1190000012767617?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

你真的会在async/await中捕获异常吗?

原文链接:Catching without Awaiting

当执行一项需要等待一段时间才能返回的任务时,如果使用async/await,就显得比较麻烦了。如果async方法还没有得到返回值,我们就捕获不到其中的异常。

在我的上一篇文章Learn to Throw Again中写到,当使用async/await时,如何同时捕获到回调函数和throw抛出的错误。在这篇文章中,我们将讨论如何在“后台”中执行异步操作并捕获异常(这里使用双引号,因为在单线程平台上没有真正的后台操作)

从回调函数的模式开始,思考下列代码:

function email(user, message, callback) {
  if (!user) {
    // 抛出异常
    throw new Error('Invlid user');
  }
  if (!user.address) {
    // 回调函数,可能抛出异常
    return callback();
  }
  // 异步的
  return mailer.send(user.address, message, callback);
}

上述代码遵循典型的throw-on-bad-input / callback-asynchronous-errors模式(一旦程序接收到错误的输入,异步抛出异常),如果我们想要发出一封邮件,我们这样调用:

email(user, message, () => {});

对于非法的输入,调用这个函数依旧可能抛出异常。但是,如果电子邮件在传输中产生错误,这个函数调用时会忽略异步抛出的错误。

我们把它改为Promise的版本:

function email(user, message) {
  if (!user) {
    throw new Error('Invlid user');
  }
  if (!user.address) {
    return Promise.resolve();
  }
  return mailer.send(user.address, message); // 函数返回一个Promise
}

这样,对于非法的输入,依旧可以捕获到异常。而对于mailer.send()操作则会返回一个Promise,我们能够轻松地通过Promise.catch()捕获到异常:

email(user, message).catch(() => {});

不管是回调函数还是Promise,他们都是异步的,我们的应用程序都不会因为email发送而被阻塞。

对于async/await的模式,如果在try...catch语句中不使用await关键字,那么try...catch子句不会真正工作。来看下面的async版本:

function email(user, message) {
  if (!user) {
    throw new Error('Invlid user');
  }
  if (!user.address) {
    return;
  }
  return mailer.send(user.address, message); // async function
}

如果我们像这样去调用:

try {
  email(user, message);
} catch (err) {
  Bounce.rethrow(err, 'system');
}

对于非法的输入错误,仍然会正常地抛出异常,这没问题。但是对于任何异步返回的异常,例如在mailer.send()抛出的异常,则会被忽略掉。不管这种错误我们想不想捕获到,反正都是捕获不到的。为了修补这个bug,则要使用await关键字。但是问题来了,这将会导致整个“后台操作”的阻塞。

有一种方案是混用async/awaitPromise

email(user, message).catch(() => {});

但这样的问题在于,对于没有address的用户,这个方法返回的返回值类型并不是Promise,因而其也不会有catch()方法,因此程序会出现TypeError: Cannot read property ‘catch’ of undefined这样的错误。

你可能会尝试直接把email()函数声明为async函数, 并使得它一定会返回一个Promise,但是这并不是一个很好的解决方案,因为async / await其实也只是Promise对象的一层包装。如果不使用await关键字,把一个函数声明为async函数是完全没有必要的。因为async函数总是要通过返回一个Promise,通过next-tick拿到结果,这样会浪费Promise包装和next-tick事件循环机制所造成的性能损耗。

此外,如果要在循环中使用async函数,并且这个循环中执行了很多任务,但是其实很多任务并不是真正意义上异步的,那就没有必要使用async / await,可以参考hapi.js中的checking if you really need to await下列代码判断是否真的需要使用await,这样或许能获得一些性能的提升:

var response = (typeof func === 'function' ? func(this) : this._invoke(func));
if (response && typeof response.then === 'function') { // Skip await if no reason to
  response = await response;
}

判断是否真的需要await,其实就是判断其是否存在then方法,并且then方法是一个函数。因为await的作用其实就是取得一个异步操作的返回结果。

如果你能够保证email方法总是返回一个Promise,我们可以通过更改我们的email()函数来达到这一点,但这样就显得急功近利了!代码显得十分不简洁,而且使用了很不必要的异步操作。在一个完整的async/await函数调用栈中,不需要我们手动构建Promise。对于这个例子来说还好,更重要的是,我们不可能总通过改变email()方法来实现,因为这只是一个例子,在实际运用中,可能email()方法是通过模块引入的。

其中一种解决方案是通过await关键字来调用async函数。通常情况下,在一个函数中使用阻塞操作,如果不等待这个函数执行完成,它不会抛出异常,但是我们可以通过try...catch来包裹:

async function backgroundEmail(user, message) {
  try {
    await email(user, message);
  } catch (err) {
    Bounce.rethrow(err, 'system');
  }
}

然后不通过await调用backgroundEmail

backgroundEmail(user, message);

这样我们不但能够捕获到应用程序的异常,还能够捕获到异步抛出的异常。

为了让异常捕获更加简单,我们使用Bounce模块,它提供了一个background()方法。

Bounce.background(() => email(user, message));

如果我们使用Node.jsAssertionError原型,这样就能够使得Bounce抛出输入异常的错误了。

async/await函数去除了一些同步函数(() => {})的功能,为了达到和普通函数相同的效果,我们不得不写一些额外的代码来实现。但是使用新的工具库,可以很简便地突破这一限制。


Recommend

  • 67
    • 掘金 juejin.im 6 years ago
    • Cache

    谈谈前端异常捕获与上报

    关于 微信公众号:前端呼啦圈(Love-FED) 我的博客:劳卜的博客 知乎专栏:前端呼啦圈 前言 Hello,大家好,又与大家见面了,这次给大家分享下前端异常监控中需要了解的异常捕获与上报机制的一些要点,同时包含了实战性质的参考代码和流程。 首先,我们为

  • 48
    • droidyue.com 5 years ago
    • Cache

    JVM 如何处理未捕获异常

    继之前的文章详解JVM如何处理异常,今天再次发布一篇比较关联的文章,如题目可知,今天聊一聊在JVM中线程遇到未捕获异常的问题,其中涉及到线程如何处理未捕获异常和一些内容介绍。 什么是未捕获异常 未捕获异常指...

  • 44

  • 39
    • 掘金 juejin.im 4 years ago
    • Cache

    捕获 React 异常

    此项目为云音乐营收组稳定性工程的前端部分,本文作者 章伟东,项目其他参与者赵祥涛 一个 bug 引发的血案 韩国某著名男子天团之前在我们平台上架了一张重磅数字专辑,本来是一件喜大普奔的好事,结果上架后投诉蜂拥而至。部分用户反馈页面打开就崩溃,紧急排查后...

  • 11

    1. ANR 产生原理 关于 ANR 的触发原因,Android 官方开发者文档中 “What Triggers ANR?” 有介绍,如下: Generally, the system displays an ANR if an application cannot respond to user input. For example, if a...

  • 6
    • 微信 mp.weixin.qq.com 3 years ago
    • Cache

    前端JavaScript 常见的报错及异常捕获

    在开发中,有时,我们花了几个小时写的Js 代码,在游览器调试一看,控制台一堆红,瞬间一万头草泥马奔腾而来。 至此,本文主要记录Js 常见的一些错误类型,以及常见的报错信息,分析其报错原因,并给予处理方法。并且将介绍几种捕获异常的方...

  • 9

    简介当程序崩溃(Crash)的时候,默认是不对异常信息做处理的。如果想要把异常信息保存到本地文件中,或上传的服务器。那么就要借助UncaughtExceptionHandler这个类。 使用方法

  • 6

    逄增宝 一、前言 二、函数语法介绍 与 goto 语句比较 与 fork 函数比较 与 Python 语言中的 yield/resume 比较 三、利用 setjmp/longjmp 实现异常捕获 四、利用 setjmp/longjmp 实现协程 五、...

  • 4
    • www.zoo.team 3 years ago
    • Cache

    前端异常的捕获与处理

    文章目录 二、异常分类三、异常处理四、异常分析1. JS 代码错误2. JS 语法错误3. 异步错误...

  • 5

    不知道大家项目里面是怎么处理 async/await 的异常,我斗胆在我们项目里翻了一下,发现大量使用 try-catch 来处理 async/await 异常。首先说明一下, try-catch 处理并没有什么问题,我只是觉得这么写代码会有点乱,感觉代码逻辑像是断层了一样,不易理解;

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK