自定义错误及扩展错误

更新日期: 2019-06-26阅读: 2.3k标签: 错误

当我们在进行开发的时候,通常需要属于我们自己的错误类来反映任务中可能出现的特殊情况。对于网络操作错误,我们需要 HttpError,对于数据库操作错误,我们需要 DbError,对于搜索操作错误,我们需要 NotFoundError,等等。

我们自定义的错误应该具有基本的错误属性,例如 message,name 以及更加详细的 stack。但是它们也会有属于自己的属性。举个例子,HttpError 对象会有一个 statusCode 属性,取值可能为 404、403 或 500 等。

JavaScript 允许我们在使用 throw 时带任何参数,所以从技术层面上说,我们自定义的错误不需要继承 Error 类,但如果我们继承了这个类,就能使用 obj instanceof Error 来鉴别错误对象,所以我们最好继承它。

在我们进行开发时,我们自己的异常类通常是有层次结构的,例如 HttpTimeoutError 可能继承自 HttpError 等。


扩展错误

让我们用一个能够读取用户数据的函数 readUser(json) 来作为例子。

这里是一个可用的 json 的例子:

let json = `{ "name": "John", "age": 30 }`;

在这里面,我们使用 JSON.parse。如果它接收到错误的 json,就会抛出 SyntaxError。

但即使是格式正确的 json,也并不表示它就是可用的,对吧?它有可能会遗漏一些必要的数据。例如,缺失了对用户所必需的 name 和 age 属性。

函数 readUser(json) 不仅会读取 JSON,也会检查(验证)数据。如果没有所需要的字段,或者格式不正确,那也是错误。而这不是 SyntaxError,因为数据在语法上是正确的,但是有其他的错误。我们称之为 ValidationError 并且为之创建一个类。这种类型的错误也应该承载缺少的字段的信息。

我们的 ValidationError 类应该继承自内置的 Error 类。

Error 类是内置的,但是我们需要看一下大致的代码,来理解我们需要扩展什么。

代码如下:

// 由JavaScript本身定义的内置错误类“伪代码”class Error {
  constructor(message) {
    this.message = message;
    this.name = "Error"; //(不同内置错误类别的名称)
    this.stack = <nested calls>; // 非标准,但大多数环境支持它
  }
}

现在让我们开始用 ValidationError 来进行继承:

class ValidationError extends Error {
  constructor(message) {
    super(message); // (1)
    this.name = "ValidationError"; // (2)
  }}
function test() {
  throw new ValidationError("Whoops!");
}
try {
  test();
} 
catch(err) {
  alert(err.message); // Whoops!
  alert(err.name); // 验证错误
  alert(err.stack); // 每个行编号的嵌套调用列表}

来看看构造器:

  • 行 (1) 被称为父类构造器。JavaScript 需要我们在子类构造器中调用 super,这是强制性的。父类构造器设定 message 属性。

  • 父类构造器也设定 name 的值为 “Error”,所以在行 (2) 我们将其重置为正确的值

让我们用 readUser(json) 来试试:

class ValidationError extends Error {
 constructor(message) {
   super(message);
   this.name = "ValidationError";
 }}// Usagefunction 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 实例try {
 let user = readUser('{ "age": 25 }');} catch (err) {
 if (err instanceof ValidationError) {
   alert("Invalid data: " + err.message); // 无效的数据:缺失字段:name
 } else if (err instanceof SyntaxError) { // (*)
   alert("JSON Syntax Error: " + err.message);
 } else {
   throw err; // 未知错误,再次抛出(**)
 }}

上面的 try..catch 代码块同时处理我们的 ValidationError 和来自 JSON.parse 的内置 SyntaxError。

接下来看看我们是如何使用 instanceof 来检测行 (*) 中的特定错误类型。

也看看 err.name,就像这样:

// ...// instead of (err instanceof SyntaxError)} else if (err.name == "SyntaxError") { // (*)// ...

使用 instanceof 的做法会好很多,因为我们在以后会扩展 ValidationError,创造一个它的子类型,例如 PropertyRequiredError。而 instanceof 对于新的继承类也适用。所以这是个长远的保证。

还有一点很重要,在 catch 语句捕捉到未知的错误时,它会在抛出行 (**) 处重新抛出,catch 语句仅仅知道如何处理验证和语法错误,而其他错误(代码中的打印错误等)不应该被捕获。


更进一步的继承

ValidationError 类是十分通用的。因此可能会在某些方面出错。属性可能缺失,格式可能发生错误(例如 age 属性的值为一个字符串)。让我们来创造一个更加具体的类 PropertyRequiredError,为属性缺失的错误而量身定做的。它将会承载属性缺失的相关信息。

class ValidationError extends Error {
 constructor(message) {
   super(message);
   this.name = "ValidationError";
 }}class PropertyRequiredError extends ValidationError {
 constructor(property) {
   super("No property: " + property);
   this.name = "PropertyRequiredError";
   this.property = property;
 }}// Usagefunction readUser(json) {
 let user = JSON.parse(json);

 if (!user.age) {
   throw new PropertyRequiredError("age");
 }
 if (!user.name) {
   throw new PropertyRequiredError("name");
 }

 return user;}// try..catch 实例try {
 let user = readUser('{ "age": 25 }');} catch (err) {
 if (err instanceof ValidationError) {
   alert("Invalid data: " + err.message); // 无效的数据:缺失属性:name
   alert(err.name); // PropertyRequiredError
   alert(err.property); // name
 } else if (err instanceof SyntaxError) {
   alert("JSON Syntax Error: " + err.message);
 } else {
   throw err; // 未知错误,再次抛出
 }}

这个 PropertyRequiredError 十分容易上手:我们只需要传递属性名:new PropertyRequiredError(property)。易懂的 message 属性将会由构造器提供。

需要注意的是,在 PropertyRequiredError 构造器中的 this.name 是再次进行手动赋值的。这可能会造成冗余 —— 在创建每个自定义错误的时候都要进行赋值 this.name = <class name>。但这并不是唯一的办法。我们可以创建自己的“基础异常”类,通过将 this.constructor.name 赋值给 this.name 来卸下我们肩上的负担,然后再进行继承。

我们称其为 MyError。

这是 MyError 以及其他自定义错误类的代码:

class MyError extends Error {
 constructor(message) {
   super(message);
   this.name = this.constructor.name;
 }}class ValidationError extends MyError { }class PropertyRequiredError extends ValidationError {
 constructor(property) {
   super("No property: " + property);
   this.property = property;
 }}// name is correct
alert( new PropertyRequiredError("field").name ); // PropertyRequiredError

现在的自定义错误更加的简洁,特别是 ValidationError,我们在其构造器中删除了 “this.name = …” 这一行。


包装异常

上述代码中的函数 readUser 的目的就是“读取用户数据”,对吧?在此过程中可能会出现多个不同类型的异常,目前我们有 SyntaxError 和 ValidationError,但在将来,函数 readUser 将会不断壮大,新添加的代码或许会导致其他类型的异常。

调用函数 readUser 的代码要能够处理这些异常。现在它在 catch 语句块中使用多个 if 语句来检测不同类型的异常以及抛出未知异常。但如果函数 readUser 抛出了多种异常 —— 我们扪心自问:我们真的需要一个接一个地处理它抛出的异常吗?

通常答案是 “No”:外部代码想要比其他代码更高一级。它想要一些类似于“数据读取异常“的东西。它为什么发生 —— (其错误描述信息)通常是不相关的。或者,如果能有一种获取异常细节的办法就更好了,但这仅限于我们需要的时候。

所以,我们创建一个 ReadError 类来表现上述的异常。如果在函数 readUser 中发生了异常,我们会将其捕获,并生成 ReadError。我们同时也会在其 cause 属性中保留对原始异常的引用。那么外部的代码就只需要检测 ReadError。

下面的代码定义了 ReadError ,并演示了如何在 readUser 和 try..catch 中使用它:

class ReadError extends Error {
 constructor(message, cause) {
   super(message);
   this.cause = cause;
   this.name = 'ReadError';
 }}class ValidationError extends Error { /*...*/ }class PropertyRequiredError extends ValidationError { /* ... */ }function validateUser(user) {
 if (!user.age) {
   throw new PropertyRequiredError("age");
 }

 if (!user.name) {
   throw new PropertyRequiredError("name");
 }}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;
   }
 }}try {
 readUser('{bad json}');} catch (e) {
 if (e instanceof ReadError) {
   alert(e);
   // 原错误:语法错误:在位置 1 处不应有 b
   alert("Original error: " + e.cause);
 } else {
   throw e;
 }}

上述代码中,readUser 正如描述的一样正常工作 —— 捕获语法以及验证的异常并且抛出 ReadError 异常用来代替之前的行为(未知的异常依旧重新抛出)。

所以外部代码负责检测 instanceof ReadError,不必列出所有可能的异常类型。

这种途径称为“包装异常”,因为我们将“低级别的异常”包装为 ReadError,使得调用代码更加抽象和方便。它在面向对象编程中被广泛使用。


总结

  • 我们能够正常地继承 Error 以及其他内置的错误类,只需要注意 name 属性以及不要忘了调用 super。

  • 大多数时候,我们应该使用 instanceof 来检测一些特定的异常。它也能够在继承中使用。但有时我们会发现来自第三方库的异常,并且不容易得到它的类。那么 name 属性就可用于这一类的检测。

  • 包装异常是一种广泛应用的技术,当一个函数处理低级别的异常时,用一个高级别的对象来报告错误。低级别的异常有时会变成这个对象的属性,就像上面例子中的 err.cause,但这并不严格要求。


英文:Ilya Kantor  译文:LeviDing
https://zh.javascript.info/custom-errors


链接: https://www.fly63.com/article/detial/3909

解决Cannot read property range of null 错误

vue工程npm run serve/start/dev启动时,node_modules文件报:Cannot read property range of null 错误,该问题是babel-eslint版本更新问题导致的;

HTTP 400 错误 - 请求无效 (Bad request)

在ajax请求后台数据时有时会报 HTTP 400 错误 - 请求无效 (Bad request);出现这个请求无效报错说明请求没有进入到后台服务里;原因:前端提交数据的字段名称或者是字段类型和后台的实体类不一致

js异步错误捕获

我们都知道 try catch 无法捕获 setTimeout 异步任务中的错误,那其中的原因是什么。以及异步代码在 js 中是特别常见的,我们该怎么做才比较?

不能执行已释放Script的代码

父页面初始化声明变量a为数组(数组对象是引用类型,赋值传递的是地址),创建iframe子页面后给父页面变量a赋值,赋值后销毁iframe子页面,再次调用变量a的时候就会抛出异常‘SCRIPT5011:不能执行已释放Script的代码’。

JS错误处理:前端JS/Vue/React/Iframe/跨域/Node

js错误的实质,也是发出一个事件,处理他,error实例对象message:错误提示信息,name:错误名称(非标准属性)宿主环境赋予

nodejs提示 cross-device link not permitted, rename 错误解决方法

文件上传的功能时候,调用fs.renameSync方法错误,这个提示是跨区重命名文件出现的权限问题。先从源文件拷贝到另外分区的目标文件,然后再unlink,就可以了。

Js中使用innerHTML的缺点是什么?

如果在JavaScript中使用innerHTML,缺点是:内容随处可见;不能像“追加到innerHTML”一样使用;innerHTML不提供验证,因此我们可能会在文档中插入有效的和破坏性的HTML并将其中断

Web前端开发,必须规避的8个错误点!

现在,有越来越多所谓的“教程”来帮助我们提高网站的易用性。我们收集了一些在Web开发中容易出错和被忽略的小问题,并且提供了参考的解决方案,以便于帮助Web开发者更好的完善网站。

web前端错误监控

为什么要做前端错误监控?1. 为了保证产品的质量2. 有些问题只存在于线上特定的环境3. 后端错误有监控,前端错误没有监控,前端错误分为两类: 即时运行错误和资源加载错误

Js中最常见的错误

最近查看了一些项目后,发现了几个最常见的JavaScript 错误。我们会告诉你什么原因导致了这些错误,以及如何防止这些错误发生。如果你能够避免落入这些 “陷阱”,你将会成为一个更好的开发者。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!