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

原文连接:Catching without Awaitingjavascript

当执行一项须要等待一段时间才能返回的任务时,若是使用async/await,就显得比较麻烦了。若是async方法尚未获得返回值,咱们就捕获不到其中的异常。java

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

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

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模式(一旦程序接收到错误的输入,异步抛出异常),若是咱们想要发出一封邮件,咱们这样调用:api

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

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

咱们把它改成Promise的版本:async

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

相关文章
相关标签/搜索