You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 1-js/08-error-handling/1-try-catch/article.md
+11-4Lines changed: 11 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -575,11 +575,11 @@ In the code above, an error inside `try` always falls out, because there's no `c
575
575
The information from this section is not a part of the core Javascript.
576
576
```
577
577
578
-
Let's imagine we've got a fatal error outside of `try..catch`, and the script died. Like a programming error that no `try..catch` doesn't know how to handle, or something else terrible.
578
+
Let's imagine we've got a fatal error outside of `try..catch`, and the script died. Like a programming error or something else terrible.
579
579
580
-
Is there a way to react on such happening? We may want to log the error, show something to the user (normally he doesn't see the error message) etc.
580
+
Is there a way to react on such a happening? We may want to log the error, show something to the user (normally he doesn't see error messages) etc.
581
581
582
-
There is none in the specification, but environments usually provide it, because it's really handy. For instance, Node.JS has [process.on('uncaughtException')](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property. It will run in case of an uncaught error.
582
+
There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.JS has [process.on('uncaughtException')](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property. It will run in case of an uncaught error.
583
583
584
584
The syntax:
585
585
@@ -621,7 +621,14 @@ For instance:
621
621
622
622
The role of the global handler `window.onerror` is usually not to recover the script execution -- that's probably impossible in case of programming errors, but to send the error message to developers.
623
623
624
-
There are also web-services that provide error-logging facilities for such cases, like <https://errorception.com> or <http://www.muscula.com>. They give a script with custom `window.onerror` function, and once inserted into a page, it reports about all errors it gets to their server. Afterwards developers can browse them and get notifications on email about fresh errors.
624
+
There are also web-services that provide error-logging for such cases, like <https://errorception.com> or <http://www.muscula.com>.
625
+
626
+
They work like this:
627
+
628
+
1. We register at the service and get a piece of JS (or a script URL) from them to insert on pages.
629
+
2. That JS script has a custom `window.onerror` function.
630
+
3. When an error occurs, it sends a network request about it to the service.
631
+
4. We can log in to the service web interface and see errors.
Copy file name to clipboardExpand all lines: 1-js/08-error-handling/2-custom-errors/article.md
+34-31Lines changed: 34 additions & 31 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,44 +1,45 @@
1
1
# Custom errors, extending Error
2
2
3
-
When we develop our software, we need our own error classes. For network operations we may need `HttpError`, for database operations `DbError`, for searching operations `NotFoundError` and so on.
3
+
When we develop something, we often need our own error classes to reflect specific things that may go wrong in our tasks. For errors in network operations we may need `HttpError`, for database operations `DbError`, for searching operations `NotFoundError` and so on.
4
4
5
-
Our errors should inherit from with the basic `Error` class and have basic error properties like `message`, `name` and, preferably, `stack`. But they also may have other properties of their own, e.g. `HttpError` objects may have `statusCode` property, that is `404`for the "page not found" error.
5
+
Our errors should inherit from basic `Error` class and support basic error properties like `message`, `name` and, preferably, `stack`. But they also may have other properties of their own, e.g. `HttpError` objects may have `statusCode` property with a value like `404`or `403` or `500`.
6
6
7
-
Technically, we can use standalone classes for errors, because Javascript allows to use `throw` with any argument. But if we inherit from `Error`, then we can use `obj instanceof Error` check to identify error objects. So it's better to inherit from it.
7
+
Technically, we can use standalone classes for our errors, because Javascript allows to use `throw` with any argument. But if we inherit from `Error`, then it becomes possible to use `obj instanceof Error` check to identify error objects. So it's better to inherit from it.
8
8
9
9
As we build our application, our own errors naturally form a hierarchy, for instance `HttpTimeoutError` may inherit from `HttpError`. Examples will follow soon.
10
10
11
11
## Extending Error
12
12
13
-
As an example, let's create a function `readUser(json)` that should read JSON with user data. We are getting that data from a remote server or, maybe it may be altered by a visitor, or just for the sheer safety -- we should to be aware of possible errors in `json`.
13
+
As an example, let's consider a function `readUser(json)` that should read JSON with user data.
14
14
15
15
Here's an example of how a valid `json` may look:
16
16
```js
17
17
let json =`{ "name": "John", "age": 30 }`;
18
18
```
19
19
20
-
If the function receives malformed `json`, then it should throw `SyntaxError`. Fortunately, `JSON.parse` does exactly that.
20
+
If `JSON.parse`receives malformed `json`, then it throws `SyntaxError`. But even if `json` is syntactically correct, it may don't have the necessary data. For instance, if may not have `name` and `age` properties that are essential for our users.
21
21
22
-
...But if the `json` is correct, that doesn't mean it has all the data. For instance, if may not have `name` or `age`.
22
+
That's called "data validation" -- we need to ensure that the data has all the necessary fields. And if the validation fails, then it not really a `SyntaxError`, because the data is syntactically correct. Let's create `ValidationError` -- the error object of our own with additional information about the offending field.
23
23
24
-
That's called "data validation" -- we need to ensure that the data has all the necessary fields. And if the validation fails, then throwing `SyntaxError` would be wrong, because the data is syntactically correct. So we should throw `ValidationError` -- the error object of our own with the proper message and, preferable, with additional information about the offending field.
25
-
26
-
Let's make the `ValidationError` class. But to better understand what we're extending -- here's the approximate code for built-in [Error class](https://tc39.github.io/ecma262/#sec-error-message):
24
+
Our `ValidationError` should inherit from the built-in `Error` class. To better understand what we're extending -- here's the approximate code for built-in [Error class](https://tc39.github.io/ecma262/#sec-error-message):
27
25
28
26
```js
27
+
// "pseudocode" for the built-in Error class defined by Javascript itself
29
28
classError {
30
29
constructor(message) {
31
30
this.message= message;
32
-
this.name="Error"; // (different names for different built-in errors)
33
-
this.stack=<sequence ofnested calls>; // non-standard! most environments support it
31
+
this.name="Error"; // (different names for different built-in error classes)
32
+
this.stack=<nested calls>; // non-standard, but most environments support it
34
33
}
35
34
}
36
35
```
37
36
38
37
Now let's inherit from it:
39
38
40
39
```js run untrusted
40
+
*!*
41
41
classValidationErrorextendsError {
42
+
*/!*
42
43
constructor(message) {
43
44
super(message); // (1)
44
45
this.name="ValidationError"; // (2)
@@ -58,11 +59,10 @@ try {
58
59
}
59
60
```
60
61
61
-
Notes:
62
-
63
-
1. In the line `(1)` we call the parent constructor to set the message. Javascript requires us to call `super` in the child constructor anyway.
64
-
2. The `name` property for our own errors should be assigned manually, otherwise it would be set by the superclass (to `"Error"`).
62
+
Please note:
65
63
64
+
1. In the line `(1)` we call the parent constructor to set the message. Javascript requires us to call `super` in the child constructor.
65
+
2. The parent constructor sets the `name` property to `"Error"`, so here we reset it to the right value.
66
66
67
67
Let's try to use it in `readUser(json)`:
68
68
@@ -105,15 +105,15 @@ try {
105
105
}
106
106
```
107
107
108
-
Everything works -- both our `ValidationError` and the built-in `SyntaxError` from `JSON.parse`are correctly handled.
108
+
Everything works -- both our `ValidationError` and the built-in `SyntaxError` from `JSON.parse`can be generated and handled.
109
109
110
-
Please note how the code check for the error type in `catch (err) { ... }`. We could use `if (err.name == "ValidationError")`, but `if (err instanceof ValidationError)` is much better, because in the future we are going to extend `ValidationError`, make new subtypes of it, namely `PropertyRequiredError`. And `instanceof` check will continue to work. So that's futureproof.
110
+
Please take a look at how the code checks for the error type in `catch (err) { ... }`. We could use `if (err.name == "ValidationError")`, but `if (err instanceof ValidationError)` is much better, because in the future we are going to extend `ValidationError`, make new subtypes of it, namely `PropertyRequiredError`. And `instanceof` check will continue to work. So that's future-proof.
111
111
112
-
Also it's important that if we meet an unknown error, then we just rethrow it. The `catch` only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or such) should fall through.
112
+
Also it's important that if `catch` meets an unknown error, then it rethrows it. The `catch` only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or such) should fall through.
113
113
114
114
## Further inheritance
115
115
116
-
The `ValidationError` class is very generic. Let's make a more concrete class `PropertyRequiredError`, exactly for the absent properties. It will carry additional information about the property that's missing.
116
+
The `ValidationError` class is very generic. Many things may be wrong. The property may be absent or it may be in a wrong format (like a string value for `age`). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing.
117
117
118
118
```js run
119
119
classValidationErrorextendsError {
@@ -166,13 +166,11 @@ try {
166
166
}
167
167
```
168
168
169
-
The new class `PropertyRequiredError` is easier to use, because we just pass the property name to it: `new PropertyRequiredError(property)`. The human-readable `message` is generated by the constructor.
170
-
171
-
Plese note that `this.name` in `PropertyRequiredError` once again assigned manually. We could evade that by using `this.constructor.name` for `this.name` in the superclass.
169
+
The new class `PropertyRequiredError` is easy to use: we only need to pass the property name: `new PropertyRequiredError(property)`. The human-readable `message` is generated by the constructor.
172
170
173
-
The generic solution would be to make `MyError`class that takes care of it, and inherit from it.
171
+
Plese note that `this.name` in `PropertyRequiredError` once again assigned manually. We could make our own "basic error" class, name it `MyError` that removes this burden from our shoulders by using `this.constructor.name` for `this.name` in the constructor. And then inherit from it.
174
172
175
-
For instance:
173
+
Here we go:
176
174
177
175
```js run
178
176
classMyErrorextendsError {
@@ -193,21 +191,21 @@ class PropertyRequiredError extends ValidationError {
Now the inheritance became simpler, as we got rid of the `"this.name = ..."` line in the constructor.
199
199
200
200
## Wrapping exceptions
201
201
202
-
The purpose of the function `readUser` in the code above is -- to "read the user data", right? There may occur different kinds of errors in the process, not only `SyntaxError` and `ValidationError`, but probably others if we continue developing it.
202
+
The purpose of the function `readUser` in the code above is "to read the user data", right? There may occur different kinds of errors in the process. Right now we have `SyntaxError` and `ValidationError`, but there may appear more if we put more stuff into it.
203
203
204
-
Right now the code which calls `readUser` uses multiple `if` in `catch` to check for different error types and rethrow if the error is unknown.
204
+
Right now the code which calls `readUser` uses multiple `if` in `catch` to check for different error types. The important questions is: do we really want to check for all error types one-by-one every time we call `readUser`?
205
205
206
-
But the important questions is: do we want to check for all these types every time we call `readUser`?
206
+
Often the answer is: "No". The outer code wants to be "one level above all that". It wants to have some kind of "data reading error". Why exactly it happened -- is usually irrelevant (the message has the info). Or, even better if there is a way to get more details, but only if we need to.
207
207
208
-
Often the answer is: "No". The outer code wants to be "one level above all that". It wants to have some kind of "data reading error", and why exactly it happened -- is usually irrelevant (the message has the info). Or, even better if there is a way to get more details, but only if it wants to.
209
-
210
-
In our case, when a data-reading error occurs, we will create an object of the new class `ReadError`, that will provide the proper message. And we'll also keep the original error in its `cause` property, just in case.
208
+
So let's make a new class `ReadError` to represent such errors. If an error occurs inside `readUser`, we'll catch it there and generate `ReadError`. We'll also keep the reference to the original error in the `cause` property.
211
209
212
210
```js run
213
211
classReadErrorextendsError {
@@ -237,21 +235,25 @@ function readUser(json) {
237
235
try {
238
236
user =JSON.parse(json);
239
237
} catch (err) {
238
+
*!*
240
239
if (err instanceofSyntaxError) {
241
240
thrownewReadError("Syntax Error", err);
242
241
} else {
243
242
throw err;
244
243
}
244
+
*/!*
245
245
}
246
246
247
247
try {
248
248
validateUser(user);
249
249
} catch (err) {
250
+
*!*
250
251
if (err instanceof ValidationError) {
251
252
thrownewReadError("Validation Error", err);
252
253
} else {
253
254
throw err;
254
255
}
256
+
*/!*
255
257
}
256
258
257
259
}
@@ -271,8 +273,9 @@ try {
271
273
}
272
274
```
273
275
274
-
The approach is called "wrapping exceptions", because we take "low level exceptions" and "wrap" them into `ReadError`that is more abstract and more convenient to use for the calling code.
276
+
In the code above, `readUser` does exactly as described -- catches syntax and validation errors and throws `ReadError`errors instead (unknown errors are rethrown as usual).
275
277
278
+
The approach is called "wrapping exceptions", because we take "low level exceptions" and "wrap" them into `ReadError` that is more abstract and more convenient to use for the calling code. It is widely used in object-oriented programming.
0 commit comments