minor fixes
This commit is contained in:
parent
4a22c9896b
commit
8c5fa3a871
1 changed files with 32 additions and 23 deletions
|
@ -5,22 +5,22 @@ libs:
|
|||
|
||||
# IndexedDB
|
||||
|
||||
IndexedDB is a built-in database, much more powerful than `localStorage`.
|
||||
IndexedDB is a database, that is built into browser, much more powerful than `localStorage`.
|
||||
|
||||
- Key/value storage: value can be (almost) anything, multiple key types.
|
||||
- Stores almost any kind of values by keys, multiple key types.
|
||||
- Supports transactions for reliability.
|
||||
- Supports key range queries, indexes.
|
||||
- Can store much more data than `localStorage`.
|
||||
- Can store much bigger volumes of data than `localStorage`.
|
||||
|
||||
That power is usually excessive for traditional client-server apps. IndexedDB is intended for offline apps, to be combined with ServiceWorkers and other technologies.
|
||||
|
||||
The native interface to IndexedDB, described in the specification <https://www.w3.org/TR/IndexedDB>, is event-based.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Open database
|
||||
|
||||
To start working with IndexedDB, we first need to open a database.
|
||||
To start working with IndexedDB, we first need to `open` (connect to) a database.
|
||||
|
||||
The syntax:
|
||||
|
||||
|
@ -33,20 +33,22 @@ let openRequest = indexedDB.open(name, version);
|
|||
|
||||
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.
|
||||
|
||||
After the call, we need to listen to events on `openRequest` object:
|
||||
The call returns `openRequest` object, we should listen to events on it:
|
||||
- `success`: database is ready, there's the "database object" in `openRequest.result`, that we should use it for further calls.
|
||||
- `error`: opening failed.
|
||||
- `upgradeneeded`: database is ready, but its version is outdated (see below).
|
||||
|
||||
**IndexedDB has a built-in mechanism of "schema versioning", absent in server-side databases.**
|
||||
|
||||
Unlike server-side databases, IndexedDB is client-side, the data is stored in the browser, so we, developers, don't have direct access to it. But when we publish a new version of our app, we may need to update the database.
|
||||
Unlike server-side databases, IndexedDB is client-side, the data is stored in the browser, so we, developers, don't have "any time" access to it. So, when we published a new version of our app, and the user visits our webpage, we may need to update the database.
|
||||
|
||||
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.
|
||||
|
||||
The event also triggers when the database did not exist yet, so we can perform initialization.
|
||||
The `upgradeneeded` event also triggers when the database did not exist yet (technically, it's version is `0`), so we can perform initialization.
|
||||
|
||||
When we first publish our app, we open it with version `1` and perform the initialization in `upgradeneeded` handler:
|
||||
Let's say we published the first version of our app.
|
||||
|
||||
Then we can open the database with version `1` and perform the initialization in `upgradeneeded` handler like this:
|
||||
|
||||
```js
|
||||
let openRequest = indexedDB.open("store", *!*1*/!*);
|
||||
|
@ -66,7 +68,9 @@ openRequest.onsuccess = function() {
|
|||
};
|
||||
```
|
||||
|
||||
When we publish the 2nd version:
|
||||
Then, later, we publish the 2nd version.
|
||||
|
||||
We can open it with version `2` and perform the upgrade like this:
|
||||
|
||||
```js
|
||||
let openRequest = indexedDB.open("store", *!*2*/!*);
|
||||
|
@ -85,9 +89,9 @@ openRequest.onupgradeneeded = function() {
|
|||
};
|
||||
```
|
||||
|
||||
So, in `openRequest.onupgradeneeded` we update the database. Soon we'll see how it's done. And then, only if its handler finishes without errors, `openRequest.onsuccess` triggers.
|
||||
Please note: as our current version is `2`, `onupgradeneeded` handler has a code branch for version `0`, suitable for users that come for the first time and have no database, and also for version `1`, for upgrades.
|
||||
|
||||
After `openRequest.onsuccess` we have the database object in `openRequest.result`, that we'll use for further operations.
|
||||
And then, only if `onupgradeneeded` handler finishes without errors, `openRequest.onsuccess` triggers, and the database is considered successfully opened.
|
||||
|
||||
To delete a database:
|
||||
|
||||
|
@ -96,29 +100,32 @@ let deleteRequest = indexedDB.deleteDatabase(name)
|
|||
// deleteRequest.onsuccess/onerror tracks the result
|
||||
```
|
||||
|
||||
```warn header="Can we open an old version?"
|
||||
Now what if we try to open a database with a lower version than the current one? E.g. the existing DB version is 3, and we try to `open(...2)`.
|
||||
```warn header="We can't open an older version of the database"
|
||||
If the current user database has a higher version than in the `open` call, e.g. the existing DB version is `3`, and we try to `open(...2)`, then that's an error, `openRequest.onerror` triggers.
|
||||
|
||||
That's an error, `openRequest.onerror` triggers.
|
||||
That's odd, but such thing may happen when a visitor loaded an outdated JavaScript code, e.g. from a proxy cache. So the code is old, but his database is new.
|
||||
|
||||
Such thing may happen if the visitor loaded an outdated code, e.g. from a proxy cache. We should check `db.version`, suggest him to reload the page. And also re-check our caching headers to ensure that the visitor never gets old code.
|
||||
To protect from errors, we should check `db.version` and suggest him to reload the page. Use proper HTTP caching headers to avoid loading the old code, so that you'll never have such problem.
|
||||
```
|
||||
|
||||
### Parallel update problem
|
||||
|
||||
As we're talking about versioning, let's tackle a small related problem.
|
||||
|
||||
Let's say, a visitor opened our site in a browser tab, with database version 1.
|
||||
Let's say:
|
||||
1. A visitor opened our site in a browser tab, with database version `1`.
|
||||
2. Then we rolled out an update, so our code is newer.
|
||||
3. And then the same visitor opens our site in another tab.
|
||||
|
||||
Then we rolled out an update, and the same visitor opens our site in another tab. So there are two tabs, both with our site, but one has an open connection with DB version 1, while the other one attempts to update it in `upgradeneeded` handler.
|
||||
So there's a tab with an open connection to DB version `1`, while the second tab one attempts to update it to version `2` in its `upgradeneeded` handler.
|
||||
|
||||
The problem is that a database is shared between two tabs, as that's the same site, same origin. And it can't be both version 1 and 2. To perform the update to version 2, all connections to version 1 must be closed.
|
||||
The problem is that a database is shared between two tabs, as it's the same site, same origin. And it can't be both version `1` and `2`. To perform the update to version `2`, all connections to version 1 must be closed, including the one in the first tab.
|
||||
|
||||
In order to organize that, the `versionchange` event triggers an open database object when a parallel upgrade is attempted. We should listen to it, so that we should close the database (and probably suggest the visitor to reload the page, to load the updated code).
|
||||
In order to organize that, the `versionchange` event triggers in such case on the "outdated" database object. We should listen to it and close the old database connection (and probably suggest the visitor to reload the page, to load the updated code).
|
||||
|
||||
If we don't close it, then the second, new connection will be blocked with `blocked` event instead of `success`.
|
||||
If we don't listen to `versionchange` event and don't close the old connection, then the second, new connection won't be made. The `openRequest` object will emit the `blocked` event instead of `success`. So the second tab won't work.
|
||||
|
||||
Here's the code to do that:
|
||||
Here's the code to correctly handle the parallel upgrade:
|
||||
|
||||
```js
|
||||
let openRequest = indexedDB.open("store", 2);
|
||||
|
@ -141,7 +148,9 @@ openRequest.onsuccess = function() {
|
|||
|
||||
*!*
|
||||
openRequest.onblocked = function() {
|
||||
// there's another open connection to same database
|
||||
// this event shouldn't trigger if we handle onversionchange correctly
|
||||
|
||||
// it means that there's another open connection to same database
|
||||
// and it wasn't closed after db.onversionchange triggered for them
|
||||
};
|
||||
*/!*
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue