翻译:疯狂的技术宅原文: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 stringDeprecationWarning: 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.<anonymous> (/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欢迎关注,每天都给你推送新鲜的前端技术文章