资讯专栏INFORMATION COLUMN

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

baiy / 573人阅读

摘要:在我的上一篇文章中写到,当使用时,如何同时捕获到回调函数和抛出的错误。而对于操作则会返回一个,我们能够轻松地通过捕获到异常不管是回调函数还是,他们都是异步的,我们的应用程序都不会因为发送而被阻塞。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90782.html

相关文章

  • 前端er,真的会用 async

    摘要:异步函数是值通过事件循环异步执行的函数,它会通过一个隐式的返回其结果。 async 异步函数 不完全使用攻略 前言 现在已经到 8012 年的尾声了,前端各方面的技术发展也层出不穷,VueConf TO 2018 大会 也发布了 Vue 3.0的计划。而在我们(我)的日常中也经常用 Vue 来编写一些项目。那么,就少不了 ES6 的登场了。那么话说回来,你真的会用 ES6 的 asyn...

    Jaden 评论0 收藏0
  • JavaScript基础——深入学习async/await

    摘要:等待的基本语法该关键字的的意思就是让编译器等待并返回结果。这里并不会占用资源,因为引擎可以同时执行其他任务其他脚本或处理事件。接下来,我们写一个火箭发射场景的小例子不是真的发射火箭 本文由云+社区发表 本篇文章,小编将和大家一起学习异步编程的未来——async/await,它会打破你对上篇文章Promise的认知,竟然异步代码还能这么写! 但是别太得意,你需要深入理解Promise后,...

    张金宝 评论0 收藏0
  • [翻译] Async/Await 使的代码更简洁

    摘要:取而代之,利用事件循环体系,使用了一种类似语法的工作方式一旦非阻塞的异步操作完成之后,就可以让开发者分配的回调函数被触发。第一个尝试嵌套的回调函数下面是使用嵌套的回调函数的实现方法这可能对于任何使用者来说再熟悉不过了。 写在文章前 这篇文章翻译自 ASYNC/AWAIT WILL MAKE YOUR CODE SIMPLER,这是一篇写于2017年八月的文章,并由某专栏提名为17年十大...

    hightopo 评论0 收藏0
  • 如何优雅地处理Async/Await异常

    摘要:能够捕获非异步的异常。来匹配正常异常的情况。在中处理所有的异常如果出错,则退出。所以,的模式使得异常处理变得非常简洁。自从年双十一正式上线,累计处理了亿错误事件,付费客户有阳光保险核桃编程荔枝掌门对微脉青团社等众多品牌企业。 译者按: 使用.catch()来捕获所有的异常 原文: Async Await Error Handling in JavaScript 译者: Fundeb...

    villainhr 评论0 收藏0
  • JavaScript 工作原理之四-事件循环及异步编程的出现和 5 种更好的 async/await

    摘要:函数会在之后的某个时刻触发事件定时器。事件循环中的这样一次遍历被称为一个。执行完毕并出栈。当定时器过期,宿主环境会把回调函数添加至事件循环队列中,然后,在未来的某个取出并执行该事件。 原文请查阅这里,略有改动。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第四章。 现在,我们将会通过回顾单线程环境下编程的弊端及如何克服这些困难以创建令人惊叹...

    maochunguang 评论0 收藏0

发表评论

0条评论

baiy

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<