您的位置:首页 > Web前端 > JavaScript

Javascript自定义错误,继承Error

2017-07-30 10:02 197 查看

Javascript自定义错误,继承Error

在开发过程中,经常需要我们自己的错误类,用于描述任务中可能发生错误的特别内容。如网络操作错误,可能需要
HttpError
,数据库操作错误
DbError
以及搜索操作错误
NotFoundError
等等。

我们的错误应该支持基本的错误属性,如:
message
,
name
以及更详细的
stack
,也可能有其他属性,如
HttpError
对象可能有
statusCode
属性,如:
404
403
500


Javascript使用throw可以带任何参数,所以技术上自定义错误不需要继承
Error
,但通过继承,可以使用
obj instanceof Error
去区别错误对象,所以最好使用继承。

当我们搭建应用时,我们的错误自然形成层次结构,举例,
HttpTimeoutError
可能继承自
HttpError
等。

扩展Error

举例,我们考虑函数
readUser(json)
可以读取用户数据,下面是一个有效的json数据。

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


我们自然使用
JSON.parse
,如果接收到畸形的json,则会抛出
SyntaxError
错误。

即使json是语法正确的,也不意味是有有效的user,对吗?可以丢失有效的数据,举例,如果没有基本的name和age属性。

函数
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)我们调用父类构造函数,Javascript需要我们在子类构造函数中调用super,这时强制的。父类构造函数设置message属性。

父类构造函数也设置name属性为
Error
,所以行(2)我们重置为正确的值。

现在尝试在
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
方式更好,因为未来可以可能会继续扩展
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;
}
}

// 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)
。构造函数生成人类易懂的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
并演示在
try...catch
块中使用
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
如描述的一样工作正常——捕获syntax和validation错误,然后抛出ReadError错误,代替之前的未知错误重新抛出。

所以外部代码检查
instanceof ReadError
,无需列出所有类型的错误。

这种方法称为“包装异常”,因为我们获得“低级别的异常”并包装至
ReadError
,对调用代码来说,更抽象更方便。在面向对象编程中广泛使用。

总结

通常可以从
Error
或其他的内置错误类中继承,只需关心name属性,不要忘记调用super。

大多数时,应该使用instanceof检查特定错误,也支持继承类。但有时有错误对象来自第三方库,不容易获得其类,那么name属性可以被使用。

包装异常被普遍使用,当函数处理低级别异常,并使一个更高级别的对象报告错误,低级别异常有时编程对象属性,如上面示例中的
err.cause
,但没有严格规定。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: