Skip to content

Commit f1bf4f1

Browse files
committed
fixes
1 parent 3c37e1d commit f1bf4f1

File tree

4 files changed

+52
-61
lines changed

4 files changed

+52
-61
lines changed

1-js/06-advanced-functions/11-currying-partials/article.md

+10-15
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ To understand the benefits we definitely need a worthy real-life example.
170170

171171
Advanced currying allows the function to be both callable normally and partially.
172172

173-
For instance, we have the logging function `log(date, importance, message)` that formats and outputs the information. In real projects such functions also have many other useful features like sending logs over the network:
173+
For instance, we have the logging function `log(date, importance, message)` that formats and outputs the information. In real projects such functions also have many other useful features like sending logs over the network, here we just use `alert`:
174174

175175
```js
176176
function log(date, importance, message) {
@@ -184,34 +184,29 @@ Let's curry it!
184184
log = _.curry(log);
185185
```
186186

187-
After that `log` still works the normal way:
188-
189-
```js
190-
log(new Date(), "DEBUG", "some debug");
191-
```
192-
193-
...But also can be called in the curried form:
187+
After that `log` work both the normal way and in the curried form:
194188

195189
```js
190+
log(new Date(), "DEBUG", "some debug"); // log(a,b,c)
196191
log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
197192
```
198193

199-
Let's get a convenience function for today's logs:
194+
Now we can easily make a convenience function for current logs:
200195

201196
```js
202-
// todayLog will be the partial of log with fixed first argument
203-
let todayLog = log(new Date());
197+
// currentLog will be the partial of log with fixed first argument
198+
let currentLog = log(new Date());
204199

205200
// use it
206-
todayLog("INFO", "message"); // [HH:mm] INFO message
201+
currentLog("INFO", "message"); // [HH:mm] INFO message
207202
```
208203

209-
And now a convenience function for today's debug messages:
204+
And here's a convenience function for current debug messages:
210205

211206
```js
212-
let todayDebug = todayLog("DEBUG");
207+
let todayDebug = currentLog("DEBUG");
213208

214-
todayDebug("message"); // [HH:mm] DEBUG message
209+
currentLog("message"); // [HH:mm] DEBUG message
215210
```
216211

217212
So:

1-js/11-async/07-microtask-queue/article.md

-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ Naturally, `promise` shows up first, because `setTimeout` macrotask awaits in th
124124

125125
As a logical consequence, macrotasks are handled only when promises give the engine a "free time". So if we have a chain of promise handlers that don't wait for anything, execute right one after another, then a `setTimeout` (or a user action handler) can never run in-between them.
126126

127-
128127
## Unhandled rejection
129128

130129
Remember "unhandled rejection" event from the chapter <info:promise-error-handling>?

2-ui/1-document/08-styles-and-classes/article.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ So we can operate both on the full class string using `className` or on individu
6666
Methods of `classList`:
6767

6868
- `elem.classList.add/remove("class")` -- adds/removes the class.
69-
- `elem.classList.toggle("class")` -- if the class exists, then removes it, otherwise adds it.
69+
- `elem.classList.toggle("class")` -- adds the class if it doesn't exist, otherwise removes it.
7070
- `elem.classList.contains("class")` -- returns `true/false`, checks for the given class.
7171

72-
Besides that, `classList` is iterable, so we can list all classes like this:
72+
Besides, `classList` is iterable, so we can list all classes with `for..of`, like this:
7373

7474
```html run
7575
<body class="main page">
@@ -147,7 +147,7 @@ To set the full style as a string, there's a special property `style.cssText`:
147147
</script>
148148
```
149149

150-
We rarely use it, because such assignment removes all existing styles: it does not add, but replaces them. May occasionally delete something needed. But still can be done for new elements when we know we won't delete something important.
150+
This property is rarely used, because such assignment removes all existing styles: it does not add, but replaces them. May occasionally delete something needed. But we can safely use it for new elements, when we know we won't delete an existing style.
151151

152152
The same can be accomplished by setting an attribute: `div.setAttribute('style', 'color: red...')`.
153153
````

6-data-storage/03-indexeddb/article.md

+39-42
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ That power is usually excessive for traditional client-server apps. IndexedDB is
1616

1717
The native interface to IndexedDB, described in the specification <https://www.w3.org/TR/IndexedDB>, is event-based.
1818

19-
We can also use `async/await` with the help of a promise-based wrapper, like <https://github.com/jakearchibald/idb>. That's pretty convenient, but the wrapper is not perfect, it can't replace events for all cases, so we'll start with events, and then use the wrapper.
19+
We can also use `async/await` with the help of a promise-based wrapper, like <https://github.com/jakearchibald/idb>. That's pretty convenient, but the wrapper is not perfect, it can't replace events for all cases. So we'll start with events, and then, after we gain understanding of IndexedDb, we'll use the wrapper.
2020

2121
## Open database
2222

23-
To start working with IndexedDB, we need to open a database.
23+
To start working with IndexedDB, we first need to open a database.
2424

2525
The syntax:
2626

@@ -31,16 +31,16 @@ let openRequest = indexedDB.open(name, version);
3131
- `name` -- a string, the database name.
3232
- `version` -- a positive integer version, by default `1` (explained below).
3333

34-
We can have many databases with different names, all within the current origin (domain/protocol/port). So different websites can't access databases of each other.
34+
We can have many databases with different names, but all of them exist within the current origin (domain/protocol/port). Different websites can't access databases of each other.
3535

3636
After the call, we need to listen to events on `openRequest` object:
37-
- `success`: database is ready, use the database object `openRequest.result` for further work.
37+
- `success`: database is ready, there's the "database object" in `openRequest.result`, that we should use it for further calls.
3838
- `error`: open failed.
3939
- `upgradeneeded`: database version is outdated (see below).
4040

4141
**IndexedDB has a built-in mechanism of "schema versioning", absent in server-side databases.**
4242

43-
Unlike server-side databases, IndexedDB is client-side, we don't have the data at hands. But when we publish a new version of our app, we may need to update the database.
43+
Unlike server-side databases, IndexedDB is client-side, in the browser, so we don't have the data at hands. But when we publish a new version of our app, we may need to update the database.
4444

4545
If the local database version is less than specified in `open`, then a special event `upgradeneeded` is triggered, and we can compare versions and upgrade data structures as needed.
4646

@@ -109,11 +109,13 @@ An example of object that can't be stored: an object with circular references. S
109109

110110
**There must be an unique `key` for every value in the store.**
111111

112-
A key must have a type one of: number, date, string, binary, or array. It's a unique object identifier: we can search/remove/update values by the key.
112+
A key must have a type one of: number, date, string, binary, or array. It's an unique identifier: we can search/remove/update values by the key.
113113

114114
![](indexeddb-structure.png)
115115

116-
We can provide a key when we add an value to the store, similar to `localStorage`. That's good for storing primitive values. But when we store objects, IndexedDB allows to setup an object property as the key, that's much more convenient. Or we can auto-generate keys.
116+
As we'll see very soon, we can provide a key when we add an value to the store, similar to `localStorage`. But when we store objects, IndexedDB allows to setup an object property as the key, that's much more convenient. Or we can auto-generate keys.
117+
118+
But we need to create an object store first.
117119

118120
The syntax to create an object store:
119121
```js
@@ -127,7 +129,7 @@ Please note, the operation is synchronous, no `await` needed.
127129
- `keyPath` -- a path to an object property that IndexedDB will use as the key, e.g. `id`.
128130
- `autoIncrement` -- if `true`, then the key for a newly stored object is generated automatically, as an ever-incrementing number.
129131

130-
If we don't supply any options, then we'll need to provide a key explicitly later, when storing an object.
132+
If we don't supply `keyOptions`, then we'll need to provide a key explicitly later, when storing an object.
131133

132134
For instance, this object store uses `id` property as the key:
133135
```js
@@ -136,22 +138,24 @@ db.createObjectStore('books', {keyPath: 'id'});
136138

137139
**An object store can only be created/modified while updating the DB version, in `upgradeneeded` handler.**
138140

139-
That's a technical limitation. Outside of the handler we'll be able to add/remove/update the data, but object stores are changed only during version update.
141+
That's a technical limitation. Outside of the handler we'll be able to add/remove/update the data, but object stores can be created/removed/altered only during version update.
142+
143+
To perform database version upgrade, there are two main approaches:
144+
1. We can implement per-version upgrade functions: from 1 to 2, from 2 to 3, from 3 to 4 etc. Then, in `upgradeneeded` we can compare versions (e.g. old 2, now 4) and run per-version upgrades step by step, for every intermediate version (2 to 3, then 3 to 4).
145+
2. Or we can just examine the database: get a list of existing object stores as `db.objectStoreNames`. That object is a [DOMStringList](https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#domstringlist) that provides `contains(name)` method to check for existance. And then we can do updates depending on what exists and what doesn't.
140146

141-
To do an upgrade, there are two main ways:
142-
1. We can compare versions and run per-version operations.
143-
2. Or we can get a list of existing object stores as `db.objectStoreNames`. That object is a [DOMStringList](https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#domstringlist), and it provides `contains(name)` method to check for the existance. And then we can do updates depending on what exists.
147+
For small databases the second path may be simpler.
144148

145149
Here's the demo of the second approach:
146150

147151
```js
148152
let openRequest = indexedDB.open("db", 1);
149153

150-
// create an object store for books if not exists
154+
// create/upgrade the database without version checks
151155
openRequest.onupgradeneeded = function() {
152156
let db = openRequest.result;
153-
if (!db.objectStoreNames.contains('books')) {
154-
db.createObjectStore('books', {keyPath: 'id'});
157+
if (!db.objectStoreNames.contains('books')) { // if there's no "books" store
158+
db.createObjectStore('books', {keyPath: 'id'}); // create it
155159
}
156160
};
157161
```
@@ -188,14 +192,14 @@ db.transaction(store[, type]);
188192
- `store` is a store name that the transaction is going to access, e.g. `"books"`. Can be an array of store names if we're going to access multiple stores.
189193
- `type` – a transaction type, one of:
190194
- `readonly` -- can only read, the default.
191-
- `readwrite` -- can only read and write, but not modify object stores.
195+
- `readwrite` -- can only read and write the data, but not create/remove/alter object stores.
192196

193197
There'is also `versionchange` transaction type: such transactions can do everything, but we can't create them manually. IndexedDB automatically creates a `versionchange` transaction when opening the database, for `updateneeded` handler. That's why it's a single place where we can update the database structure, create/remove object stores.
194198

195-
```smart header="What are transaction types for?"
199+
```smart header="Why there exist different types of transactions?"
196200
Performance is the reason why transactions need to be labeled either `readonly` and `readwrite`.
197201
198-
Many `readonly` transactions can access concurrently the same store, but `readwrite` transactions can't. A `readwrite` transaction "locks" the store for writing. The next transaction must wait before the previous one finishes before accessing the same store.
202+
Many `readonly` transactions are able to access concurrently the same store, but `readwrite` transactions can't. A `readwrite` transaction "locks" the store for writing. The next transaction must wait before the previous one finishes before accessing the same store.
199203
```
200204

201205
After the transaction is created, we can add an item to the store, like this:
@@ -227,12 +231,12 @@ request.onerror = function() {
227231
};
228232
```
229233

230-
There are basically four steps:
234+
There were basically four steps:
231235

232236
1. Create a transaction, mention all stores it's going to access, at `(1)`.
233237
2. Get the store object using `transaction.objectStore(name)`, at `(2)`.
234238
3. Perform the request to the object store `books.add(book)`, at `(3)`.
235-
4. ...Handle request success/error `(4)`, make other requests if needed, etc.
239+
4. ...Handle request success/error `(4)`, then we can make other requests if needed, etc.
236240

237241
Object stores support two methods to store a value:
238242

@@ -242,30 +246,24 @@ Object stores support two methods to store a value:
242246
- **add(value, [key])**
243247
Same as `put`, but if there's already a value with the same key, then the request fails, and an error with the name `"ConstraintError"` is generated.
244248

245-
Just like when opening a database, we send a request: `books.add(book)`, and then wait for `success/error` events.
249+
Similar to opening a database, we can send a request: `books.add(book)`, and then wait for `success/error` events.
246250

247251
- The `request.result` for `add` is the key of the new object.
248252
- The error is in `request.error` (if any).
249253

250-
## Transactions autocommit
254+
## Transactions' autocommit
251255

252-
In the example above we started the transaction and made `add` request. We could make more requests. How do we finish ("commit") the transaction?
256+
In the example above we started the transaction and made `add` request. But as we stated previously, a transaction may have multiple associated requests, that must either all success or all fail. How do we mark the transaction as finished, no more requests to come?
253257

254258
The short answer is: we don't.
255259

256260
In the next version 3.0 of the specification, there will probably be a manual way to finish the transaction, but right now in 2.0 there isn't.
257261

258262
**When all transaction requests are finished, and the [microtasks queue](info:microtask-queue) is empty, it is committed automatically.**
259263

260-
```smart header="What's an \"empty microtask queue\"?"
261-
The microtask queue is explained in [another chapter](info:async-await#microtask-queue). In short, an empty microtask queue means that for all settled promises their `.then/catch/finally` handlers are executed.
264+
Usually, we can assume that a transaction commits when all its requests are complete, and the current code finishes.
262265

263-
In other words, handling of finished promises and resuming "awaits" is done before closing the transaction.
264-
265-
That's a minor technical detail. If we're using `async/await` instead of low-level promise calls, then we can assume that a transaction commits when all its requests are done, and the current code finishes.
266-
```
267-
268-
So, in the example above no special code is needed to finish the transaction.
266+
So, in the example above no special call is needed to finish the transaction.
269267

270268
Transactions auto-commit principle has an important side effect. We can't insert an async operation like `fetch`, `setTimeout` in the middle of transaction. IndexedDB will not keep the transaction waiting till these are done.
271269

@@ -331,9 +329,9 @@ That's to be expected, not only because of possible errors at our side, but also
331329

332330
**A failed request automatically aborts the transaction, canceling all its changes.**
333331

334-
Sometimes a request may fail with a non-critical error. We'd like to handle it in `request.onerror` and continue the transaction. Then, to prevent the transaction abort, we should call `event.preventDefault()`.
332+
In some situations, we may want to handle the failure (e.g. try another request), without canceling existing changes, and continue the transaction. That's possible. The `request.onerror` handler is able to prevent the transaction abort by calling `event.preventDefault()`.
335333

336-
In the example below a new book is added with the same key (`id`). The `store.add` method generates a `"ConstraintError"` in that case. We handle it without canceling the transaction:
334+
In the example below a new book is added with the same key (`id`) as the existing one. The `store.add` method generates a `"ConstraintError"` in that case. We handle it without canceling the transaction:
337335

338336
```js
339337
let transaction = db.transaction("books", "readwrite");
@@ -347,6 +345,7 @@ request.onerror = function(event) {
347345
if (request.error.name == "ConstraintError") {
348346
console.log("Book with such id already exists"); // handle the error
349347
event.preventDefault(); // don't abort the transaction
348+
// use another key for the book?
350349
} else {
351350
// unexpected error, can't handle it
352351
// the transaction will abort
@@ -396,9 +395,9 @@ request.onerror = function(event) {
396395

397396
## Searching by keys
398397

399-
There are two main ways to search in an object store:
398+
There are two main types of search in an object store:
400399
1. By a key or a key range. That is: by `book.id` in our "books" storage.
401-
2. By another object field, e.g. `book.price`. We need an index for that.
400+
2. By another object field, e.g. `book.price`.
402401

403402
First let's deal with the keys and key ranges `(1)`.
404403

@@ -524,7 +523,7 @@ Indexes are internally sorted by the tracked object field, `price` in our case.
524523

525524
## Deleting from store
526525

527-
The `delete` method looks up values to delete by a query, just like `getAll`.
526+
The `delete` method looks up values to delete by a query, the call format is similar to `getAll`:
528527

529528
- **`delete(query)`** -- delete matching values by query.
530529

@@ -555,9 +554,7 @@ books.clear(); // clear the storage.
555554

556555
Methods like `getAll/getAllKeys` return an array of keys/values.
557556

558-
But an object storage can be huge, bigger than the available memory.
559-
560-
Then `getAll` will fail to get all records as an array.
557+
But an object storage can be huge, bigger than the available memory. Then `getAll` will fail to get all records as an array.
561558

562559
What to do?
563560

@@ -608,7 +605,7 @@ request.onsuccess = function() {
608605
The main cursor methods are:
609606

610607
- `advance(count)` -- advance the cursor `count` times, skipping values.
611-
- `continue([key])` -- advance the cursor to the next value in range matching or after key.
608+
- `continue([key])` -- advance the cursor to the next value in range matching (or immediately after `key` if given).
612609

613610
Whether there are more values matching the cursor or not -- `onsuccess` gets called, and then in `result` we can get the cursor pointing to the next record, or `undefined`.
614611

@@ -672,7 +669,7 @@ So we have all the sweet "plain async code" and "try..catch" stuff.
672669

673670
### Error handling
674671

675-
If we don't catch the error, then it falls through, just as usual.
672+
If we don't catch an error, then it falls through, till the closest outer `try..catch`.
676673

677674
An uncaught error becomes an "unhandled promise rejection" event on `window` object.
678675

@@ -688,7 +685,7 @@ window.addEventListener('unhandledrejection', event => {
688685

689686
### "Inactive transaction" pitfall
690687

691-
A we know already, a transaction auto-commits as soon as the browser is done with the current code and microtasks. So if we put an *macrotask* like `fetch` in the middle of a transaction, then the transaction won't wait for it to finish. It just auto-commits. So the next request in it fails.
688+
A we know already, a transaction auto-commits as soon as the browser is done with the current code and microtasks. So if we put an *macrotask* like `fetch` in the middle of a transaction, then the transaction won't wait for it to finish. It just auto-commits. So the next request in it would fail.
692689

693690
For a promise wrapper and `async/await` the situation is the same.
694691

0 commit comments

Comments
 (0)