JavaScript错误处理之扩展Error

627 阅读5分钟

「这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

前言

我们前面已经说过了try...catch的用法,当出现错误时,catch会返回err信息。它主要由Error类型名称+描述+调用栈组成。

其中【错误类型】默认是使用JS自带的,但是当我们在开发某些东西时,经常会需要我们自己的 error 类来反映在我们的任务中可能出错的特定任务。比如网络错误 HttpError,数据库错误 DbError,搜索错误 NotFoundError,那么现在我们就来看看怎么自定义自己的Error。

image-20211121211304949


相关内容回顾:👉 JavaScript里的错误捕获和处理


ECMA-262规范定义的七种错误类型

执行代码期间可能会发生的错误有多种类型,每种错误都有对应的错误类型,而当错误发生时,就会抛出相应类型的错误对象。现在我们先来回顾一下ECMA-262定义的7种错误类型:

  • Error: 所有错误的基本类型,实际上不会被抛出。
  • EvalError: 执行eval错误时抛出。
  • RangeError: 数字超出边界时抛出。
  • ReferenceError: 对象不存在是抛出。
  • SyntaxError: 出现语法错误时抛出。
  • TypeError: 变量不是期望的类型时抛出。
  • URIError: 给encodeURI()等函数传递非法字符串时抛出。

扩展Error

假如当前我们想要获取带有用户数据的JSON,在函数内部,我们将使用 JSON.parse。如果它接收到格式不正确的 json,就会抛出 SyntaxError

但是,即使 json 在语法上是正确的,也不意味着该数据是有效的用户数据,对吧?因为它可能丢失了某些必要的数据。例如,对用户来说,必不可少的是 nameage 属性。

相关内容回顾:
👉 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;
}

实例运行结果:

image-20211121213858626

上面代码中的 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;
}

🍇 运行结果

image-20211121215119339

现在我们制作出了一个更具体的错误类型 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。因为当前只有SyntaxErrorValidationError,所以只需要判断两个就可以,但是如果产生多种呢?

这个时候我们需要先确定是否真的想每次都一一检查所有的 error 类型?

通常是不需要的,我们只想知道这里是否是“数据读取异常” — 为什么发生了这样的 error 通常是无关紧要的(error 信息描述了它)。或者,如果我们有一种方法能够获取 error 的详细信息那就更好了,但前提是我们需要。

我们所描述的这项技术被称为“包装异常”。

创建一个新的类 ReadError 来表示一般的“数据读取” error。

class ReadError extends Error {
  constructor(message, cause) {
    super(message);
    this.cause = cause;
    this.name = 'ReadError';
  }
}

函数readUser 将捕获内部发生的数据读取 error,例如 ValidationErrorSyntaxError,并生成一个 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 细节,那么可以检查 readUsercause 属性。

image-20211121220650289

参考资料:

Ecma-262

Custom errors, extending Error


🎨【点赞】【关注】不迷路,更多前端干货等你解锁

往期推荐

👉 一起来看看JS的原型继承

👉 JS中的getter和setter你会用吗?

👉 深入理解ES6箭头对象

👉 JS的装饰器模式实例分析