「这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战」
前言
我们前面已经说过了try...catch的用法,当出现错误时,catch会返回err信息。它主要由Error类型名称+描述+调用栈组成。
其中【错误类型】默认是使用JS自带的,但是当我们在开发某些东西时,经常会需要我们自己的 error 类来反映在我们的任务中可能出错的特定任务。比如网络错误 HttpError
,数据库错误 DbError
,搜索错误 NotFoundError
,那么现在我们就来看看怎么自定义自己的Error。
相关内容回顾:👉 JavaScript里的错误捕获和处理
ECMA-262规范定义的七种错误类型
执行代码期间可能会发生的错误有多种类型,每种错误都有对应的错误类型,而当错误发生时,就会抛出相应类型的错误对象。现在我们先来回顾一下ECMA-262定义的7种错误类型:
- Error: 所有错误的基本类型,实际上不会被抛出。
- EvalError: 执行eval错误时抛出。
- RangeError: 数字超出边界时抛出。
- ReferenceError: 对象不存在是抛出。
- SyntaxError: 出现语法错误时抛出。
- TypeError: 变量不是期望的类型时抛出。
- URIError: 给encodeURI()等函数传递非法字符串时抛出。
扩展Error
假如当前我们想要获取带有用户数据的JSON,在函数内部,我们将使用 JSON.parse
。如果它接收到格式不正确的 json
,就会抛出 SyntaxError
。
但是,即使 json
在语法上是正确的,也不意味着该数据是有效的用户数据,对吧?因为它可能丢失了某些必要的数据。例如,对用户来说,必不可少的是 name
和 age
属性。
相关内容回顾:
👉 JavaScript里Class类的基本介绍
👉 细说JavaScript里的类继承
为了解决这个问题,我们可以新建一个自定义的错误类型,假如叫ValidationError
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
可以看到我们新建的错误类继承了JavaScript里内建的 Error
类,这是因为我们是希望在JS自带的错误类型上额外增加一些验证信息。
就拿以上示例来说,我们希望除了实现接收到格式不正确之外,还会检查(“验证”)数据。
现在我们结合try...catch来实现一下这个需求
function readUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new ValidationError("No field: age");
}
if (!user.name) {
throw new ValidationError("No field: name");
}
return user;
}
实例运行结果:
上面代码中的 try..catch
块既处理我们的 ValidationError
又处理来自 JSON.parse
的内建 SyntaxError
。
深入继承
ValidationError
类是非常通用的。很多东西都可能出错。对象的属性可能缺失或者属性可能有格式错误(例如 age
属性的值为一个字符串)。因此我们还可以针对缺少属性的错误来制作一个更具体的 PropertyRequiredError
类。它将携带有关缺少的属性的相关信息。
//这个ValidationError就是上面示例中的
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("No property: " + property);
this.name = "PropertyRequiredError";
this.property = property;
}
}
📖 用法
function readUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new PropertyRequiredError("age");
}
if (!user.name) {
throw new PropertyRequiredError("name");
}
return user;
}
🍇 运行结果
现在我们制作出了一个更具体的错误类型 PropertyRequiredError
,但是可以看到,在 PropertyRequiredError
constructor 中的 this.name
是通过手动重新赋值的。如果我们要建更多的错误类型,那岂不是每个自定义 error 类中都要进行 this.name = <class name>
赋值操作。针对这种情况,我们可以通过创建自己的“基础错误(basic error)”类来避免这种情况。
class MyError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
MyError类进行了 this.name = this.constructor.name
赋值。之后就可以让所有我们自定义的 error 都从这个“基础错误”类进行继承。
现在我们可以改写一下上面的ValidationError
和PropertyRequiredError
类`
class ValidationError extends MyError { }
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("No property: " + property);
this.property = property;
}
}
这样我们就摆脱了 constructor 中的 "this.name = ..."
这一行。
包装异常
我们上面调用 了readUser
的代码应该处理 error。然后在 catch
块中使用了多个 if
语句来检查 error 类,处理已知的 error。因为当前只有SyntaxError
和 ValidationError
,所以只需要判断两个就可以,但是如果产生多种呢?
这个时候我们需要先确定是否真的想每次都一一检查所有的 error 类型?
通常是不需要的,我们只想知道这里是否是“数据读取异常” — 为什么发生了这样的 error 通常是无关紧要的(error 信息描述了它)。或者,如果我们有一种方法能够获取 error 的详细信息那就更好了,但前提是我们需要。
我们所描述的这项技术被称为“包装异常”。
创建一个新的类 ReadError
来表示一般的“数据读取” error。
class ReadError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = 'ReadError';
}
}
函数readUser
将捕获内部发生的数据读取 error,例如 ValidationError
和 SyntaxError
,并生成一个 ReadError
来进行替代。对象 ReadError
会把对原始 error 的引用保存在其 cause
属性中。
function readUser(json) {
let user;
try {
user = JSON.parse(json);
} catch (err) {
if (err instanceof SyntaxError) {
throw new ReadError("Syntax Error", err);
} else {
throw err;
}
}
try {
validateUser(user);
} catch (err) {
if (err instanceof ValidationError) {
throw new ReadError("Validation Error", err);
} else {
throw err;
}
}
}
之后,调用 readUser
的代码只需要检查 ReadError
,而不必检查每种数据读取 error。并且,如果需要更多 error 细节,那么可以检查 readUser
的 cause
属性。
参考资料:
Custom errors, extending Error
🎨【点赞】【关注】不迷路,更多前端干货等你解锁
往期推荐