Skip to content

Commit 799a7e2

Browse files
committed
up
1 parent 7ed245d commit 799a7e2

File tree

11 files changed

+410
-37
lines changed

11 files changed

+410
-37
lines changed

1-js/08-error-handling/1-try-catch/article.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -575,11 +575,11 @@ In the code above, an error inside `try` always falls out, because there's no `c
575575
The information from this section is not a part of the core Javascript.
576576
```
577577
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.
579579
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.
581581
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.
583583
584584
The syntax:
585585
@@ -621,7 +621,14 @@ For instance:
621621
622622
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.
623623
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.
625632
626633
## Summary
627634

1-js/08-error-handling/2-custom-errors/1-format-error/task.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@ importance: 5
44

55
# Inherit from SyntaxError
66

7-
Create an error `FormatError` that inherits from `SyntaxError`.
7+
Create a class `FormatError` that inherits from the built-in `SyntaxError` class.
8+
9+
It should support `message`, `name` and `stack` properties.
810

911
Usage example:
12+
1013
```js
1114
let err = new FormatError("formatting error");
1215

1316
alert( err.message ); // formatting error
1417
alert( err.name ); // FormatError
1518
alert( err.stack ); // stack
1619

17-
alert( err instanceof SyntaxError ); // true
20+
alert( err instanceof FormatError ); // true
21+
alert( err instanceof SyntaxError ); // true (because inherits from SyntaxError)
1822
```

1-js/08-error-handling/2-custom-errors/article.md

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,45 @@
11
# Custom errors, extending Error
22

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.
44

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`.
66

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.
88

99
As we build our application, our own errors naturally form a hierarchy, for instance `HttpTimeoutError` may inherit from `HttpError`. Examples will follow soon.
1010

1111
## Extending Error
1212

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.
1414

1515
Here's an example of how a valid `json` may look:
1616
```js
1717
let json = `{ "name": "John", "age": 30 }`;
1818
```
1919

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.
2121

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.
2323

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):
2725

2826
```js
27+
// "pseudocode" for the built-in Error class defined by Javascript itself
2928
class Error {
3029
constructor(message) {
3130
this.message = message;
32-
this.name = "Error"; // (different names for different built-in errors)
33-
this.stack = <sequence of nested 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
3433
}
3534
}
3635
```
3736

3837
Now let's inherit from it:
3938

4039
```js run untrusted
40+
*!*
4141
class ValidationError extends Error {
42+
*/!*
4243
constructor(message) {
4344
super(message); // (1)
4445
this.name = "ValidationError"; // (2)
@@ -58,11 +59,10 @@ try {
5859
}
5960
```
6061

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:
6563

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.
6666

6767
Let's try to use it in `readUser(json)`:
6868

@@ -105,15 +105,15 @@ try {
105105
}
106106
```
107107

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.
109109

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 future proof.
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.
111111

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.
113113

114114
## Further inheritance
115115

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.
117117

118118
```js run
119119
class ValidationError extends Error {
@@ -166,13 +166,11 @@ try {
166166
}
167167
```
168168

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.
172170

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.
174172

175-
For instance:
173+
Here we go:
176174

177175
```js run
178176
class MyError extends Error {
@@ -193,21 +191,21 @@ class PropertyRequiredError extends ValidationError {
193191
}
194192
}
195193

194+
// name is correct
196195
alert( new PropertyRequiredError("field").name ); // PropertyRequiredError
197196
```
198197

198+
Now the inheritance became simpler, as we got rid of the `"this.name = ..."` line in the constructor.
199199

200200
## Wrapping exceptions
201201

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.
203203

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`?
205205

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.
207207

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.
211209

212210
```js run
213211
class ReadError extends Error {
@@ -237,21 +235,25 @@ function readUser(json) {
237235
try {
238236
user = JSON.parse(json);
239237
} catch (err) {
238+
*!*
240239
if (err instanceof SyntaxError) {
241240
throw new ReadError("Syntax Error", err);
242241
} else {
243242
throw err;
244243
}
244+
*/!*
245245
}
246246

247247
try {
248248
validateUser(user);
249249
} catch (err) {
250+
*!*
250251
if (err instanceof ValidationError) {
251252
throw new ReadError("Validation Error", err);
252253
} else {
253254
throw err;
254255
}
256+
*/!*
255257
}
256258

257259
}
@@ -271,8 +273,9 @@ try {
271273
}
272274
```
273275

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).
275277

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.
276279

277280
## Summary
278281

0 commit comments

Comments
 (0)