Custom Errors, Extending Error
JavaScript Custom Errors, Extending Error (JavaScript自定义错误,扩展错误)
In the course of developing something, it is necessary to have own error classes for reflecting particular things that can go wrong. For errors in network operations, HttpError is necessary, for searching operations-NotFoundError, for database operations- DbError, and more. They should support basic error properties such as name, message, and stack. Also, they can include other properties. For example, HttpError objects can contain a statusCode such as 403, 404, or 500. (在开发某些东西的过程中,有必要拥有自己的错误类来反映可能出错的特定事情。对于网络操作中的错误, HttpError是必需的,用于搜索操作- NotFoundError ,用于数据库操作- DbError等。它们应支持基本错误属性,如名称、消息和堆栈。此外,它们还可以包括其他属性。例如, HttpError对象可以包含状态代码,如403、404或500。)
JavaScript allows using throw with any argument. So, technically, custom errors don’t need inheritance from Error. (JavaScript允许对任何参数使用throw。因此,从技术上讲,自定义错误不需要从Error继承。)
Extending Error
Extending Error (扩展错误)
Now, let’s discover what extending error is. To be more precise, we can start from a function readUser(json), which reads JSON with user data. (现在,让我们来了解什么是扩展错误。更准确地说,我们可以从函数readUser (json)开始,它使用用户数据读取JSON。)
So, a valid json looks like this:
let json = `{ "name": "John", "age": 25 }`;
Internally, JSON.parse is used. (在内部,使用JSON.parse。)
In case it gets malformed json then it throws SyntaxError. Even if json is syntactically correct it doesn’t consider that the user is valid. It can miss essential data such as name, age, or other properties. (如果它的json格式不正确,则会抛出SyntaxError。即使json在语法上是正确的,它也不会认为用户是有效的。它可能缺少基本数据,如姓名、年龄或其他属性。)
The readUser(json) function both reads JSON and checks the data. In case the format is wrong, then an error occurs. As the data is syntactically correct, it’s not a SyntaxError. It is a ValidationError. (ReadUser (json)函数读取JSON并检查数据。如果格式错误,则会发生错误。由于数据在语法上是正确的,因此它不是SyntaxError。这是一个ValidationError。)
For ValidationError it is necessary to inherit from the built-in Error class. (对于ValidationError ,必须从内置的Error类继承。)
Here is the code for extending:
// "Pseudocode" for the built-in Error class
class Error {
constructor(message) {
this.message = message;
this.errorName = "Error"; // different names for different built-in error classes
this.stack = < call stack > ; // non-standard, but most environments support it
}
}
In the example below, ValidationError is inherited from it:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function testFunc() {
throw new ValidationError("Oops");
}
try {
testFunc();
} catch (err) {
console.log(err.message); // Oops
console.log(err.name); // ValidationError
console.log(err.stack); // a list of nested calls, with line numbers for each
}
Now, let’s try using it in createUser(json), like this:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function createUser(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;
}
try {
let user = createUser('{ "age": 20 }');
} catch (err) {
if (err instanceof ValidationError) {
console.log("Invalid data: " + err.message); // Invalid data: No field: name
} else if (err instanceof SyntaxError) {//(*)
console.log("JSON Syntax Error: " + err.message);
} else {
throw err; // unknown error, rethrow it
}
}
So, in the code above the try..catch block handles ValidationError and SyntaxError from JSON.parse. (因此,在上面的代码中, try.. catch块处理来自JSON.parse的ValidationError和SyntaxError。)
It is especially interesting how instanceof is used for checking a particular error in the () line. (特别有趣的是,如何使用instanceof来检查()行中的特定错误。)
The err.name can also look like here:
// ...
// instead of, err instanceof SyntaxError
} else if (err.name == "SyntaxError") { // (*)
}
However, the version with instanceof is better, as the next step is to extend ValidationError, making subtypes of it. The instanceof check will keep working for new inheriting classes. (但是,使用instanceof的版本更好,因为下一步是扩展ValidationError ,使其成为子类型。instanceof检查将继续用于新的继承类。)
Further Inheritance
Further Inheritance (进一步继承)
The ValidationError class is very universal. Hence, various things can go wrong. For example, a property can be in a wrong format or it can be absent. Let’s consider the PropertyRequiredError for absent properties. It carries extra information about the missing property. Here is how it looks like:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("Not a property: " + property);
this.name = "PropertyRequiredError";
this.property = property;
}
}
function createUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new PropertyRequiredError("age");
}
if (!user.name) {
throw new PropertyRequiredError("name");
}
return user;
}
// Example with try..catch
try {
let user = createUser('{ "age": 20 }');
} catch (err) {
if (err instanceof ValidationError) {
console.log("Invalid data: " + err.message); // Invalid data: No property: name
console.log(err.name); // PropertyRequiredError
console.log(err.property); // name
} else if (err instanceof SyntaxError) {
console.log("JSON Syntax Error: " + err.message);
} else {
throw err; // unknown error, rethrow it
}
}
The new class PropertyRequiredError is easy in usage. All you need is to do is passing the property name: new PropertyRequiredError(property). The constructor creates a human-readable message.
Please, consider that this.name in PropertyRequiredError constructor is assigned manually. So, assigning this.name = <class name> in each custom error class. For avoiding that the basic error class, which assigns this.name = this.constructor.name is made. It can inherit all the custom errors. It can be called ErrorName.
Here it is and other custom error classes:
class ErrorName extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
class ValidationError extends ErrorName {}
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("Not a property: " + property);
this.property = property;
}
}
// correct name
console.log(new PropertyRequiredError("field").name); // PropertyRequiredError
So, now custom errors are shorter. (因此,现在自定义错误更短。)
Wrapping Exceptions
Wrapping Exceptions (包装异常)
The purpose of the createUser function is reading the user data. In the process, various errors may occur. (CreateUser函数的目的是读取用户数据。在此过程中,可能会出现各种错误。)
The code calling createUser must handle the errors. In the example below, it applies multiple ifs in the catch block, checking the class and handling known errors and rethrowing the unknown ones. (调用createUser的代码必须处理错误。在下面的示例中,它在catch块中应用多个if ,检查类并处理已知错误并重新抛出未知错误。)
The scheme will look as follows:
In the code above, there are two kinds of errors. Of course, they can be more. The technique, represented in this part is known as “wrapping extensions”. (在上面的代码中,有两种错误。 当然,它们可以更多。 本部分中表示的技术称为“包裹扩展”。) Let’s start at making a new class ReadError to present a generic data reading error. The readUser function catches the data reading errors, occurring inside it (for example, ValidationError and SyntaxError) and create ReadError uthuinstead. (让我们从创建新类ReadError开始,以显示通用数据读取错误。readUser函数捕获在其内部发生的数据读取错误(例如, ValidationError和SyntaxError )并创建ReadError uthuinstead。)
The ReadError object keeps the reference to the original error inside the cause property. (ReadError对象将对原始错误的引用保留在cause属性中。)
try {
//...
createUser() // the potential error source
(createUser ()//潜在错误源)
//...
}
catch (err) {
if (err instanceof ValidationError) {
// handle validation error
(//句柄验证错误)
} else if (err instanceof SyntaxError) {
// handle syntax error
(//句柄语法错误)
} else {
throw err; // unknown error, rethrow it
}
}
Let’s take a look at the code, which specifies ReadError demonstrating its usage in readUser and try..catch:
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 createUser(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 {
createUser('{bad json}');
} catch (e) {
if (e instanceof ReadError) {
console.log(e);
// Error: SyntaxError:
// Unexpected token b in JSON at position 1
(//位置1的JSON中意外的令牌b)
console.log("Error: " + e.cause);
} else {
throw e;
}
}
The approach, described above is named “wrapping exceptions” as “low-level” exceptions are taken and wrapped into ReadError, which is more abstract. (上面描述的方法被称为“包装异常” ,因为“低级”异常被接受并包装到ReadError中, ReadError更抽象。)
Summary
Summary (概要)
In brief, we can state that, normally, it is possible to inherit from Error and other built-in classes. All you need is to take care of the name property, not forgetting to call super. (简而言之,我们可以说,通常情况下,可以从Error和其他内置类继承。您只需照顾name房源,不要忘记打电话给super。)
The instanceof class can also be applied to check for specific errors. It also operates with inheritance. At times when an error object comes from a third-party library, the name property can be used. (Instanceof类还可以应用于检查特定错误。它还具有继承性。有时,当错误对象来自第三方库时,可以使用name属性。)
A widespread and useful technique is wrapping exceptions. With it, a function can handle low-level exceptions, creating high-level errors, instead of different low-level ones. (包装异常是一种广泛而有用的技术。使用它,函数可以处理低级异常,创建高级错误,而不是不同的低级错误。)