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,但没有严格规定。
相关文章推荐
- 继承QGraphicsItem 编译错误:error: no member named 'staticMetaObject' in 'QGraphicsItem'; did you mean sim
- JavaScript中的自定义对象(custom Object)和继承(inheritance)
- javascript 自定义错误信息
- java web 自定义错误页面 完整jsp错误页面代码(同时写错误日志) error.jsp
- javascript 在 IE中出现 ERROR 尚未实现 错误
- JavaScript 运行时错误: Sys.WebForms.PageRequestManagerParserErrorException: 未能分析从服务器收到的消息。
- java web 自定义错误页面 完整jsp错误页面代码(同时写错误日志) error.jsp
- JavaScript中的ReferenceError和TypeError两种错误的区别
- 常见错误:JavaScript 提交form 的时候,Error:对象不支持此属性或方法
- PHP set_error_handler() 设置用户自定义的错误处理程序
- php 自定义错误error_function 的建立
- javascript自定义对象,实现继承
- javascript 自定义错误信息
- nRF51822 自定义UUID,ble_advdata_set的时候 NRF_ERROR_DATA_SIZE 错误的解决
- javaScript自定义对象和继承的模式探究
- dedecms中自定义模型遇到错误Fatal error: Call to a member function GetInnerText() on a non-object
- JavaScript原形继承方式添加自定义格式化方法
- @@ERROR 使用 或自定义错误
- set_error_handler() 顾名思义,自定义错误的处理
- Spring Boot自定义错误页面,Whitelabel Error Page处理方式