Custom errors, extending Error
当我们在进行开发的时候,通常需要属于我们自己的错误类来反映任务中可能出现的特殊情况。对于网络操作错误,我们需要 HttpError
,对于数据库操作错误,我们需要 DbError
,对于搜索操作错误,我们需要 NotFoundError
,等等。
我们自定义的错误应该具有基本的错误属性,例如 message
,name
以及更加详细的 stack
。但是它们也会有属于自己的属性。举个例子,HttpError
对象会有一个 statusCode
属性,取值可能为 404
、403
或 500
等。
JavaScript 允许我们在使用 throw
时带任何参数,所以从技术层面上说,我们自定义的错误不需要继承 Error
类,但如果我们继承了这个类,就能使用 obj instanceof Error
来鉴别错误对象,所以我们最好继承它。
在我们进行开发时,我们自己的异常类通常是有层次结构的,例如 HttpTimeoutError
可能继承自 HttpError
等。
Extending Error
// 即使資料庫回傳的資料格式正確,也不一定可以使用,因為可能缺少我們想要的資料,像 birthday 等
let json = `{ "name": "John", "age": 30 }`;
// 內建的 Error class
// 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 extends Error
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
}
// 使用
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 (**)
}
}
// 使用 instanceof 檢測錯誤比較好,因為如果以後擴展 ValidationError
// 創造一個子 class PropertyRequiredError,instanceof 也可以適用
// ...
// instead of (err instanceof SyntaxError)
} else if (err.name == "SyntaxError") { // (*)
// ...
Further inheritance
// 創造一個更具體的 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;
}
// 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; // 未知错误,再次抛出
}
}
// 每次創造新的子 class 都要改變 this.name 的值,透過 this.constructor.name
// 可以將值傳給 this.name
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
Wrapping exceptions
// 當 readUser 函式有越來越多功能,產生錯誤也越來越多,我們無法一一處理這些錯誤,
// 創造一個外部檢測 ReadError ,在函数 readUser 中发生了異常,會生成 ReadError,
// cause 属性中保留对原始异常的引用,外部檢測 ReadError 不必列出所有可能的异常类型。
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;
}
}
Last updated