• 欢迎访问天天编码网站,Java技术、技术书单、开发工具,欢迎加入天天编码
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏天天编码吧
  • 我们的淘宝店铺已经开张了哦,传送门:https://shop145764801.taobao.com/

8.2 定制化错误,扩展错误

JS 教程 tiantian 463次浏览 0个评论 扫描二维码

当我们开发应用时,我们经常需要某些自己的错误类来反映特定的事情,比如某些特定的错误情况。对于那些发生于网络的错误,我们可能需要HttpError,对于发生于数据操作的错误是DbError,对于搜索操作的错误是NotFoundError等等。

我们的错误应该支持基本的错误属性,比如messagenamestack等等。但是,它们也应该还具有其他的属性,比如HttpError对象可能含有statusCode属性,其可能值为404403500等等。

JavaScript 允许使用throw抛出任何参数,所以在技术上而言,我们的错误对象没有必要一定继承自Error。但是,如果我们继承了该对象,那么就可以使用obj instanceof Error来辨别出错误对象。所以,最好是要继承Error

当我们创建引用时,我们的错误自然会形成一个继承体系,比如HttpTimeoutError可能就是继承自HttpError,等等。

扩展错误

举例,假设函数readUser(json)应该读取 JSON 格式的用户数据。

下面是一个合法json的示例:

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

一般地,我们会使用JSON.parse。如果它接受到了错误格式的json,那么它就会抛出SyntaxError

但是,即使json在语法上是正确的,那个并不意味值就是一个合法的用户,对吧?它可能会缺失必要的数据。举例,它可能没有nameage属性,这对于我们的用户而言是非常必要的。

我们的函数readUser(json)不仅仅需要读取 JSON数据,而且需要检查数据是否合法。如果不存在必要的属性或者域,那么就是存在一个错误。而且那不是一个SyntaxError,因为该数据在语法上是正确的,而是另一种类型的错误。我们可以称它为ValidationError,并创建一个类来表示它。一个该类的错误对象还应该携带相关域的信息。

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

那个类是内建类,但是我们可以看看它的相似代码,看看它是如何扩展的。

// The "pseudocode" for the built-in Error class defined by JavaScript itself
class Error {
  constructor(message) {
    this.message = message;
    this.name = "Error"; // (different names for different built-in error classes)
    this.stack = <nested calls>; // non-standard, but most environments support it
  }
}

现在,让我们的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); // ValidationError
  alert(err.stack); // a list of nested calls with line numbers for each
}

请注意上述的构造器:

  1. 我们需要调用父类的构造器。那个父类构造器会处理好message属性。
  2. 那个父类构造器还会将name属性设置为Error,所以,我们需要覆写它。

让我们在 readUser(json)来使用它:

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

// Usage
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;
}

// Working example with try..catch

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
    alert("Invalid data: " + err.message); // Invalid data: No field: name
  } else if (err instanceof SyntaxError) { // (*)
    alert("JSON Syntax Error: " + err.message);
  } else {
    throw err; // unknown error, rethrow it (**)
  }
}

上述的那个try..catch处理了我们自己的ValidationError和内建的SyntaxError错误。

上面我们使用了instanceof来检查特定类型的错误,当然,我们可以使用err.name,比如:

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

当然,那个instanceof版本的检查方法更加好,因为它对于未来的控制开放。如果未来加入了子类PropertyRequiredErro,那么该检查仍然可以正常工作。

而且,在catch中遇到无法处理的错误时,我们应该重新抛出该错误。

进一步继承

那个ValidationError类非常通用。很多情况都适用该对象。那个属性可能不存在,或者存在的格式不对。让我们创建更加精细的类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;
  }
}

// Usage
function readUser(json) {
  let user = JSON.parse(json);

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

  return user;
}

// Working example with try..catch

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
    alert("Invalid data: " + err.message); // Invalid data: No property: name
    alert(err.name); // PropertyRequiredError
    alert(err.property); // name
  } else if (err instanceof SyntaxError) {
    alert("JSON Syntax Error: " + err.message);
  } else {
    throw err; // unknown error, rethrow it
  }
}

那个新创建的PropertyRequiredError非常容易使用:我们只需传递属性名称给它:new PropertyRequiredError(property)。那个人类可读的错误信息由构造器自动产生。

注意,上面的那个this.name = <class name> 有一段硬编码片段。实际上,我们可以使用this.constructor.name来赋值给this.name

比如,我们的类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

现在,类的构造器代码更加简短。

包裹异常

上述readUser函数的功能就是用来读取用户数据的,对吧?在读取的过程中可能发生各种各样的错误。现在,我们就要SyntaxErrorValidationError错误,但在未来,readUser函数可能会继续生长:那些新代码可能会导致更多类型的错误。

那个调用readUser的代码应该处理那些错误。现在,我们在catch块中使用if来检查不同类型的错误,并重新抛出无法处理的异常。如果readUser在未来会产生更多错误,那么我们是否愿意一个一个的方式来检查和处理相应的错误呢?

一般而言,我们都希望可以以一种方式来处理所有的异常。特别是在我们并不关心错误的细节情况时。

现在,让我们创建一个全新的ReadError类来代表那些错误。如果readUser发生了错误,我们就扑获该错误并生成一个ReadError。我们会在其cause属性中引用那个原始的错误。那么外部的代码仅仅需要检查那个ReadErro

下面是定义ReadError类的代码,并在readUser中进行使用:

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);
    // Original error: SyntaxError: Unexpected token b in JSON at position 1
    alert("Original error: " + e.cause);
  } else {
    throw e;
  }
}

在上述代码中,readUser正如所述那样工作——扑获语法和数据错误异常并重新抛出ReadError错误。

所以,外部的代码只检查instanceof ReadError而已。 不需要列举出所有可能的错误类型。

这个处理模式被称为包裹异常,因为我们将一个低层次的异常,包裹为一个高层次的ReadError,该异常更加抽象,也更加方便外部代码的调用。它在面向对象编程中经常被使用。

总结

  • 我们可以正常地继承Error或者其他的内建错误类,只需要争取处理那个name属性,和利用super调用父类构造器。
  • 绝大多数情况下,我们应该使用instanceof来检查特定的错误。它可以与继承性协作。但是,对于第三方的错误对象,我们也可以考虑使用它的name属性来检查。
  • 在处理低层次的异常时,包裹异常是一个被广泛使用的技巧。利用高层次异常包裹它使得异常处理非常方便。

任务


继承自SyntaxError

创建一个类FormatError,该类继承自内建的SyntaxError类。

它应该支持messagenamestack属性。

使用样例:

let err = new FormatError("formatting error");

alert( err.message ); // formatting error
alert( err.name ); // FormatError
alert( err.stack ); // stack

alert( err instanceof FormatError ); // true
alert( err instanceof SyntaxError ); // true (because inherits from SyntaxError)


天天编码 , 版权所有丨本文标题:8.2 定制化错误,扩展错误
转载请保留页面地址:http://www.tiantianbianma.com/error-handler.html/
喜欢 (1)
支付宝[多谢打赏]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址