资讯专栏INFORMATION COLUMN

对JavaScript中的异步函数进行异常处理及测试

bigdevil_s / 3457人阅读

摘要:总结最后总结一下从异步函数抛出的错误不会是普通的异常。异步函数和异步方法总是返回一个,无论是已解决还是被拒绝。要拦截异步函数中的异常,必须使用。

翻译:疯狂的技术宅
原文:https://www.valentinog.com/bl...

本文首发微信公众号:jingchengyideng
欢迎关注,每天都给你推送新鲜的前端技术文章

可以在 Javascript 的异步函数中抛出错误吗?

这个话题已被反复提起过几百次,不过这次让我们从TDD(Test-Driven Development)的角度来回答它。

如果你能不在Stackoverflow上搜索就能回答这个问题,会给我留下深刻的印象。

如果不能的话也可以很酷。 继续往下读,你就能学到!

你将学到什么

通过后面的内容你将学到:

如何从 Javascript 的异步函数中抛出错误

如何使用 Jest 测试来自异步函数的异常

要求

要继续往下读你应该:

对 Javascript 和 ES6 有基本的了解

安装 Node.Js 和 Jest

如何从 Javascript 的常规函数中抛出错误

使用异常而不是返回码(清洁代码)

抛出错误是处理未知的最佳方法。

同样的规则适用于各种现代语言:Java、Javascript、Python、Ruby。

你可以从函数中抛出错误,可以参照以下示例:

function upperCase(name) {
  if (typeof name !== "string") {
    throw TypeError("name must be a string");
  }
  return name.toUpperCase();
}
module.exports = upperCase;

这是对它的测试(使用Jest):

"use strict";
const assert = require("assert");
const upperCase = require("../function");
describe("upperCase function", () => {
  test("it throws when name is not provided", () => {
    assert.throws(() => upperCase());
  });
  test("it throws when name is not a string", () => {
    assert.throws(() => upperCase(9));
  });
});

也可以从 ES6 的类中抛出错误。在 Javascript 中编写类时,我总是在构造函数中输入意外值。下面是一个例子:

class Person {
  constructor(name) {
    if (typeof name !== "string") {
      throw TypeError("name must be a string");
    }
    this.name = name;
  }
  // some method here
}
module.exports = Person;

以下是该类的测试:

"use strict";
const assert = require("assert");
const Person = require("../index");
describe("Person class", () => {
  test("it throws when name is not provided", () => {
    assert.throws(() => new Person());
  });
  test("it throws when name is not a string", () => {
    assert.throws(() => new Person(9));
  });
});

测试确实通过了:

PASS  test/index.test.js
 Person class
   ✓ it throws when name is not provided (1ms)
   ✓ it throws when name is not a string

安排的明明白白!

所以无论异常是从常规函数还是从类构造函数(或从方法)抛出的,一切都会按照预期工作。

但是如果我想从异步函数中抛出错误怎么办?

我可以在测试中使用assert.throws吗?

各位看官请上眼!

测试异常

既然都看到这里了,所以你应该知道什么是 Javascript 的异步函数,对吗?先看一段代码:

class Person {
  constructor(name) {
    if (typeof name !== "string") {
      throw TypeError("name must be a string");
    }
    this.name = name;
  }
  // some method here
}
module.exports = Person;

假设你要添加异步方法来获取有关该人的数据。这种方法需要一个网址。如果url不是字符串,就要像上一个例子中那样抛出错误。

先来修改一下这个类:

class Person {
  constructor(name) {
    if (typeof name !== "string") {
      throw TypeError("name must be a string");
    }
    this.name = name;
  }
  async getData(url) {
    if (typeof url !== "string") {
      throw TypeError("url must be a string");
    }
    // const response = await fetch(url)
    // do stuff
  }
}
module.exports = Person;

如果我运行代码会怎么样?试试吧:

const Person = require("../index");
const valentinogagliardi = new Person("valentinogagliardi");
valentinogagliardi.getData();

结果是这样

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: name must be a string
DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

果然不出所料,异步方法返回了一个Promise rejection,从严格意义上来讲,并没有抛出什么东西。错误被包含在了Promise rejection中。

换句话说,我不能使用 assert.throws 来测试它。

让我们通过测试来验证一下:

"use strict";
const assert = require("assert");
const Person = require("../index");
describe("Person methods", () => {
  test("it throws when url is not a string", () => {
    const valentinogagliardi = new Person("valentinogagliardi");
    assert.throws(() => valentinogagliardi.getData());
  });
});

测试失败了!

FAIL  test/index.test.js
  Person methods › it throws when url is not a string
   assert.throws(function)
   Expected the function to throw an error.
   But it didn"t throw anything.
   Message:
     Missing expected exception.

有没有悟出点什么?

看把你能的,来抓我啊

从严格意义上讲异步函数和异步方法不会抛出错误。异步函数和异步方法总是返回一个Promise,无论它已完成还是被拒绝,你必须附上 then() 和 catch(),无论如何。(或者将方法包装在try/catch中)。被拒绝的Promise将会在堆栈中传播,除非你抓住(catch)它

至于测试代码,应该这样写:

"use strict";
const assert = require("assert");
const Person = require("../index");
describe("Person methods", () => {
  test("it rejects when url is not a string", async () => {
    expect.assertions(1);
    const valentinogagliardi = new Person("valentinogagliardi");
    await expect(valentinogagliardi.getData()).rejects.toEqual(
      TypeError("url must be a string")
    );
  });
});

我们测试的不能是普通的异常,而是带有TypeError的rejects。

现在测试通过了:

PASS  test/index.test.js
 Person methods
   ✓ it rejects when url is not a string

那代码该怎么写呢?为了能够捕获错误,你应该这样重构:

const Person = require("../index");
const valentinogagliardi = new Person("valentinogagliardi");
valentinogagliardi
  .getData()
  .then(res => res)
  .catch(err => console.error(err));

现在异常将会出现在控制台中:

TypeError: url must be a string
    at Person.getData (/home/valentino/Documenti/articles-and-broadcasts/throw-from-async-functions-2018-04-02/index.js:12:13)
    at Object. (/home/valentino/Documenti/articles-and-broadcasts/throw-from-async-functions-2018-04-02/index.js:22:4)
    // ...

如果你想要更多的try/catch.,有一件重要的事需要注意。

下面的代码不会捕获错误:

const Person = require("../index");
async function whatever() {
  try {
    const valentinogagliardi = new Person("valentinogagliardi");
    await valentinogagliardi.getData();
    // do stuff with the eventual result and return something
  } catch (error) {
    throw Error(error);
  }
}
whatever();

记住:被拒绝的Promise会在堆栈中传播,除非你抓住(catch)它。

要在 try/catch 中正确捕获错误,可以像这样重构:

async function whatever() {
  try {
    const valentinogagliardi = new Person("valentinogagliardi");
    await valentinogagliardi.getData();
    // do stuff with the eventual result and return something
  } catch (error) {
    throw Error(error);
  }
}
whatever().catch(err => console.error(err));

这就是它的工作原理。

总结

最后总结一下:

从异步函数抛出的错误不会是“普通的异常”

异步函数和异步方法总是返回一个Promise,无论是已解决还是被拒绝。

要拦截异步函数中的异常,必须使用catch()

以下是在Jest中测试异常的规则:

使用 assert.throws 来测试普通函数和方法中的异常

使用 expect + rejects 来测试异步函数和异步方法中的异常

如果你对如何使用 Jest 测试 Koa 2 感兴趣,请查看使用Jest和Supertest进行测试的简绍这篇文章。

感谢阅读!

本文首发微信公众号:jingchengyideng


欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

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

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

相关文章

  • JavaScript中的异步函数进行异常处理测试

    摘要:总结最后总结一下从异步函数抛出的错误不会是普通的异常。异步函数和异步方法总是返回一个,无论是已解决还是被拒绝。要拦截异步函数中的异常,必须使用。 翻译:疯狂的技术宅原文:https://www.valentinog.com/bl... 本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章 可以在 Javascript 的异步函数中抛出错误吗...

    paulli3 评论0 收藏0
  • JavaScript中错误正确处理方式,你用了吗?

    摘要:单元测试会体现出以上错误处理程序的作用如果出现问题,错误处理程序就会返回。同时错误会展开堆栈,这对调试非常有帮助。展开堆栈处理异常的一种方式是在调用堆栈的顶部加入。确保你的错误处理处在相同域中,这样会保留原始消息,堆栈和自定义错误对象。 JavaScript的事件驱动范式增添了丰富的语言,也是让使用JavaScript编程变得更加多样化。如果将浏览器设想为JavaScript的事件驱动...

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

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

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

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

    浠ラ箍 评论0 收藏0
  • 我的 2015 年度小结(技术方面)

    摘要:因为路由层面受业务影响很大,经常修改一些功能的行为,所以后来大部分测试都是针对层面的单元测试。在我了解的过程中,我发现中文网络上对的讨论非常分散,于是我创建了中文社区,到年末已经有个注册用户和个帖子了。 https://jysperm.me/2016/02/programming-of-2015/ 从 2014 年末开始开发的一个互联网金融项目终于在今年三月份上线了,这是一个 Node...

    宋华 评论0 收藏0

发表评论

0条评论

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