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: 6-data-storage/03-indexeddb/article.md
+28-28Lines changed: 28 additions & 28 deletions
Original file line number
Diff line number
Diff line change
@@ -24,7 +24,7 @@ Técnicamente, los datos son almacenados bajo el directorio raíz del usuario ju
24
24
Navegadores y usuarios diferentes tendrán cada uno su propio almacenamiento independiente.
25
25
```
26
26
27
-
## Abrir una base de datos, "open"
27
+
## Apertura de una base de datos, "open"
28
28
29
29
Para empezar a trabajar con IndexedDB, primero necesitamos conectarnos o "abrir" (`open`) una base de datos.
30
30
@@ -37,7 +37,7 @@ let openRequest = indexedDB.open(name, version);
37
37
-`name` -- un string, el nombre de la base de datos.
38
38
-`version` -- un entero positivo, predeterminado en `1` (explicado más abajo).
39
39
40
-
Podemos tener muchas bases de datos con nombres diferentes, pero todas deben existir dentro del mismo origen (dominio/protocolo/puerto). Un sitio web no puede acceder bases de datos de otro.
40
+
Podemos tener muchas bases de datos con nombres diferentes, pero todas ellas existen dentro del mismo origen (dominio/protocolo/puerto). Un sitio web no puede acceder bases de datos de otro.
41
41
42
42
La llamada devuelve un objeto `openRequest`, debemos escuchar en él los eventos:
43
43
-`success`: la base de datos está lista. Hay un "objeto database" en `openRequest.result` que habremos de usar en las llamadas subsiguientes.
@@ -127,7 +127,7 @@ Entonces hay una primera pestaña con una conexión abierta a una base con versi
127
127
128
128
El problema es que la misma base está compartida entre las dos pestañas, por ser del mismo sitio y origen. Y no puede ser versión `1` y `2` al mismo tiempo. Para ejecutar la actualización a la versión `2`, todas las conexiones a la versión 1 deben ser cerradas, incluyendo las de la primera pestaña.
129
129
130
-
Para organizar esto, se dispara el evento `versionchange` (cambio-de-versión) en el objeto de base de datos. Debemos escucharlo y cerrar la conexión vieja (y probablemente sugerir una recarga de página, para cargar el código actualizado).
130
+
Para detectar estas situaciones, se dispara automáticamente el evento `versionchange` (cambio-de-versión) en el objeto de base de datos. Debemos escuchar dicho evento y cerrar la conexión vieja (y probablemente sugerir una recarga de página, para cargar el código actualizado).
131
131
132
132
Si no escuchamos el evento `versionchange` y no cerramos la conexión vieja, entonces la segunda y más nueva no se podrá hacer. El objeto `openRequest` emitirá el evento `blocked` en lugar de `success`. Entonces la segunda pestaña no funcionará.
133
133
@@ -171,21 +171,21 @@ Podemos manejar las cosas más suavemente en `db.onversionchange`, como pedirle
171
171
172
172
Como alternativa podríamos no cerrar la base en `db.onversionchange` sino usar `onblocked` de la nueva pestaña para advertirle que no puede crear una nueva conexión hasta que cierre las viejas.
173
173
174
-
Estas colisiones ocurren raramente, pero debemos al menos tener algún manejo de ella, como mínimo un manejador `onblocked` para evitar que nuestro script muera silenciosamente.
174
+
Estas colisiones ocurren raramente, pero deberíamos tener algún manejo de ella, como mínimo un manejador `onblocked` para evitar que nuestro script muera silenciosamente.
175
175
176
176
## Almacén de objetos, "store"
177
177
178
178
Para almacenar algo en IndexedDB, necesitamos un "almacén de objetos" *object store*.
179
179
180
-
Un almacén de objetos es un concepto central de IndexedDB. Contrapartes en otras bases de datos tienen "tablas" o "colecciones". Es donde los datos son almacenados. Una base de datos puede tener múltiples almacenes: una para usuarios, otra para bienes, etc.
180
+
Un almacén de objetos es un concepto central de IndexedDB. Equivale a lo que en otras bases de datos se denominan "tablas" o "colecciones". Es donde los datos son almacenados. Una base de datos puede tener múltiples almacenes: uno para usuarios, otro para bienes, etc.
181
181
182
182
A pesar de llamarse "almacén de objetos", también puede almacenar tipos primitivos.
183
183
184
184
**Podemos almacenar casi cualquier valor, incluyendo objetos complejos.**
185
185
186
186
IndexedDB usa el [algoritmo de serialización estándar](https://www.w3.org/TR/html53/infrastructure.html#section-structuredserializeforstorage) para clonar-y-almacenar un objeto. Es como `JSON.stringify` pero más poderoso, capaz de almacenar muchos tipos de datos más.
187
187
188
-
Hay objetos que no puede ser almacenados, por ejemplo los que tienen referencias circulares. Tales objetos no son serializables. `JSON.stringify` también falla con ellos.
188
+
Hay objetos que no pueden ser almacenados, por ejemplo los que tienen referencias circulares. Tales objetos no son serializables. `JSON.stringify` también falla con ellos.
189
189
190
190
**Debe haber una clave `key` única para cada valor del almacén.**
191
191
@@ -208,7 +208,7 @@ Ten en cuenta que esta operación es sincrónica, no requiere `await`.
208
208
209
209
-`name` es el nombre del almacén, por ejemplo `"books"`,
210
210
-`keyOptions` es un objeto opcional con una de estas dos propiedades:
211
-
-`keyPath` -- la ruta a un propiedad de objeto que IndexedDB usará como clave, por ejemplo `id`.
211
+
-`keyPath` -- la ruta a una propiedad del objeto que IndexedDB usará como clave, por ejemplo `id`.
212
212
-`autoIncrement` -- si es `true`, la clave para el objeto nuevo que se almacene se generará automáticamente con un número autoincremental.
213
213
214
214
Si no establecemos `keyOptions`, necesitaremos proporcionar una clave explícitamente más tarde: al momento de almacenar un objeto.
@@ -218,7 +218,7 @@ Por ejemplo, este objeto usa la propiedad `id` como clave:
218
218
db.createObjectStore('books', {keyPath:'id'});
219
219
```
220
220
221
-
**Un almacén de objetos solo puede ser creado o modificado durante la actualización de su versión. Esto es en el manejador `upgradeneeded`.**
221
+
**Un almacén de objetos solo puede ser creado o modificado durante la actualización de su versión, esto es, en el manejador `upgradeneeded`.**
222
222
223
223
Esto es una limitación técnica. Fuera del manejador podremos agregar/borrar/modificar los datos, pero los almacenes de objetos solo pueden ser creados/borrados/alterados durante la actualización de versión.
-`readonly` -- solo puede leer (es el predeterminado).
277
277
-`readwrite` -- puede leer o escribir datos (pero no crear/quitar/alterar almacenes de objetos).
278
278
279
-
También existe el tipo de transacción `versionchange`: tal transacción puede hacer de todo, pero no podemos crearla nosotros a mano. IndexedDB la crea automáticamente cuando abre la base de datos para el manejador `updateneeded`. Por ello es el único lugar donde podemos actualizar la estructura de base de datos, crear o quitar almacenes de objetos.
279
+
También existe el tipo de transacción `versionchange`: tal transacción puede hacer de todo, pero no podemos crearla nosotros a mano. IndexedDB la crea automáticamente cuando abre la base de datos para el manejador `updateneeded`. Por ello, es el único lugar donde podemos actualizar la estructura de base de datos, crear o quitar almacenes de objetos.
280
280
281
281
```smart header="¿Por qué hay diferentes tipos de transacciones?"
282
282
El rendimiento es la razón por la que necesitamos identificar las transacciones como `readonly` (lectura solamente) o `readwrite` (lectura y escritura).
283
283
284
284
Muchas transacciones `readonly` pueden leer en un mismo almacén concurrentemente, en cambio las transacciones de escritura `readwrite`, no. Una transacción `readwrite` bloquea el almacén para escribir en él. La siguiente transacción debe esperar a que la anterior termine antes de acceder al mismo almacén.
285
285
```
286
286
287
-
Una vez que la transacción es creada, podemos agregar un ítem al almacén:
287
+
Una vez que la transacción ha sido creada, podemos agregar un ítem al almacén:
288
288
289
289
```js
290
290
let transaction =db.transaction("books", "readwrite"); // (1)
@@ -378,7 +378,7 @@ En el ejemplo de arriba podemos hacer una nueva `db.transaction` justo antes de
378
378
379
379
Pero, si queremos mantener las operaciones juntas en una transacción, será mucho mejor separar las transacciones IndexedDB de la parte asincrónica.
380
380
381
-
Primero, hacer `fetch` y preparar todos los datos que fueran necesarios, y solo entonces crear una transacción y ejecutar todas las peticiones de base de datos. Así, funcionaría.
381
+
Primero, hacer `fetch` y preparar todos los datos que fueran necesarios y, solo entonces, crear una transacción y ejecutar todas las peticiones de base de datos. Así, funcionaría.
382
382
383
383
Para detectar el momento de finalización exitosa, podemos escuchar al evento `transaction.oncomplete`:
384
384
@@ -411,7 +411,7 @@ Esto es esperable, no solo por posibles errores de nuestro lado, sino también p
411
411
412
412
**Una petición fallida automáticamente aborta la transacción, cancelando todos sus cambios.**
413
413
414
-
En algunas situaciones, podemos querer manejar el fallo (por ejemplo, intentar otra petición), sin cancelar los cambios en curso, y continuar la transacción. Eso es posible. El manejador `request.onerror`está habilitado a evitar el aborto de la transacción llamando a `event.preventDefault()`.
414
+
En algunas situaciones, podemos querer manejar el fallo (por ejemplo, intentar otra petición) sin cancelar los cambios en curso, y continuar la transacción. Eso es posible. El manejador `request.onerror`es capaz de evitar el aborto de la transacción llamando a `event.preventDefault()`.
415
415
416
416
En el ejemplo que sigue, un libro nuevo es agregado con la misma clave (`id`) que otro existente. El método `store.add` genera un `"ConstraintError"` en ese caso. Lo manejamos sin cancelar la transacción:
```smart header="El almacén de objetos siempre está ordenado"
530
530
El almacén internamente guarda los valores ordenados por clave.
531
531
532
-
Entonces, las peticiones que que devuelvan muchos valoressiempre serán devueltos ordenados por clave.
532
+
Entonces, en las peticiones que devuelvan varios valores, estos siempre estarán ordenados por la clave.
533
533
```
534
534
535
535
## Buscando por cualquier campo con un índice
@@ -554,7 +554,7 @@ En nuestro ejemplo, almacenamos libros usando la propiedad `id` como clave.
554
554
555
555
Digamos que queremos buscar por precio `price`.
556
556
557
-
Primero necesitamos crear un índice. Esto debe hacerse en `upgradeneeded`, igual que ObjectStore.
557
+
Primero necesitamos crear un índice. Esto debe hacerse en `upgradeneeded`, al igual que hacíamos la creación del almacén de objetos.
558
558
559
559
```js
560
560
openRequest.onupgradeneeded=function() {
@@ -619,7 +619,7 @@ Por ejemplo:
619
619
books.delete('js');
620
620
```
621
621
622
-
Si queremos borrar libros basados en un precio u otro campo del objeto, debemos primero encontrar la clave en el índice, luego llamar a `delete` con él:
622
+
Si queremos borrar libros basados en un precio u otro campo del objeto, debemos primero encontrar la clave en el índice, luego llamar a `delete` con dicha clave:
623
623
624
624
```js
625
625
// encuentra la clave donde price = 5
@@ -644,9 +644,9 @@ Pero un almacén de objetos puede ser enorme, incluso más que la memoria dispon
644
644
645
645
¿Qué hacer?
646
646
647
-
Los cursores brindan los medios para trabajar con eso.
647
+
Los cursores brindan los medios para manejar esta situación.
648
648
649
-
**Un *cursor* es un objeto especial que, dada una consulta, recorre el almacén y devuelve un par clave/valor a la vez, así ahorrando memoria.**
649
+
**Un *cursor* es un objeto especial que, dada una consulta, recorre el almacén y devuelve un solo par clave/valor cada vez, ahorrando así memoria.**
650
650
651
651
Como un almacén está ordenado internamente por clave, un cursor lo recorre en el orden de la clave (ascendende de forma predeterminada).
652
652
@@ -662,7 +662,7 @@ let request = store.openCursor(query, [direction]);
662
662
-**`direction`** es un argumento opcional, el orden que se va a usar:
663
663
-`"next"` -- el predeterminado: el cursor recorre en orden ascendente comenzando por la clave más baja.
664
664
-`"prev"` -- el orden inverso: decrece comenzando con el registro con la clave más alta.
665
-
-`"nextunique"`, `"prevunique"` -- igual que las anteriores, pero saltando los registros con la misma clave (válido solo para cursores sobre índices. Por ejemplo, de múltiples libros con price=5, solamente el primero será devuelto).
665
+
-`"nextunique"`, `"prevunique"` -- igual que las anteriores, pero saltando los registros con la misma clave (válido solo para cursores sobre índices; por ejemplo, de múltiples libros con price=5, solamente el primero será devuelto).
666
666
667
667
**La diferencia principal del cursor es que `request.onsuccess` se dispara múltiples veces: una por cada resultado.**
668
668
@@ -693,11 +693,11 @@ Los principales métodos de cursor son:
693
693
-`advance(count)` -- avanza el cursor `count` veces, saltando valores.
694
694
-`continue([key])` -- avanza el cursor al siguiente valor en el rango o, si se provee la clave `key`, al valor inmediatamente posterior a `key`.
695
695
696
-
El evento `onsuccess` será llamado haya o no más valores coincidentes, y en `result` obtenemos el cursor apuntando al siguiente registro, o `undefined`.
696
+
El evento `onsuccess` será llamado haya o no más valores coincidentes, y en `result` obtenemos el cursor apuntando al siguiente registro o `undefined`.
697
697
698
-
En el ejemplo anterior el cursor fue hecho sobre el almacén de objetos.
698
+
En el ejemplo anterior, el cursor fue hecho sobre el almacén de objetos.
699
699
700
-
Pero también podemos hacerlo sobre un índice. Recordamos, los índices nos permiten buscar por los campos del objeto. Los cursores sobre índices hacen precisamente lo mismo que sobre el almacén de objetos: Ahorran memoria al devolver una valor a la vez.
700
+
Pero también podemos hacerlo sobre un índice. Recordamos, los índices nos permiten buscar por los campos del objeto. Los cursores sobre índices hacen precisamente lo mismo que sobre el almacén de objetos: ahorran memoria al devolver un solo valor cada vez.
701
701
702
702
Para cursores sobre índices, `cursor.key` es la clave del índice (es decir "price"), y debemos usar la propiedad `cursor.primaryKey` para la clave del objeto:
Agregar `onsuccess/onerror` a cada petición es una tarea agobiante. A veces podemos hacernos la vida más fácil usando delegación de eventos, es decir establecer manejadores para las transacciones completas, pero `async/await` es mucho más conveniente.
724
+
Agregar `onsuccess/onerror` a cada petición es una tarea agobiante. A veces podemos hacernos la vida más fácil usando delegación de eventos (por ejemplo, estableciendo manejadores para las transacciones completas), pero `async/await` es mucho más conveniente.
725
725
726
-
Usemos en adelante para este capítulo un contenedor (wrapper) liviano que añade promesas <https://github.com/jakearchibald/idb>. Este crea un objeto global `idb` con métodos IndexedDB [promisificados](info:promisify).
726
+
Usemos en adelante para este capítulo un contenedor (wrapper) liviano que añade promesas <https://github.com/jakearchibald/idb>. Este contenedor crea un objeto global `idb` con métodos IndexedDB [promisificados](info:promisify).
727
727
728
728
Entonces, en lugar de `onsuccess/onerror`, podemos escribir:
729
729
@@ -755,7 +755,7 @@ Así tenemos todo lo dulce de "código async plano" y "try..catch".
755
755
756
756
### Manejo de Error
757
757
758
-
Si no atrapamos un error, este se propaga hasta el más `try..catch` externo más cercano.
758
+
Si no atrapamos un error, este se propaga hasta el `try..catch` externo más cercano.
759
759
760
760
Un error no atrapado se vuelve un evento "rechazo de promesa no manejado" sobre el objeto `window`.
Como sabemos, una transacción se autofinaliza tan pronto como el navegador termina el código actual y las microtareas. Entonces si ponemos una *macrotarea* como `fetch` en el medio de una transacción, la transacción no esperará a que termine. Simplemente se autofinaliza. Así la siguiente petición fallaría.
775
+
Como sabemos, una transacción se autofinaliza tan pronto como el navegador termina el código actual y las microtareas. Por tanto, si ponemos una *macrotarea* como `fetch` en el medio de una transacción, la transacción no esperará a que termine. Simplemente se autofinaliza. Así la siguiente petición fallaría.
776
776
777
777
778
778
Para el contenedor de promisificación y `async/await` la situación es la misma.
El `inventory.add` que sigue a `fetch``(*)` falla con el error "transacción inactiva", porque la transacción se autocompletó y para ese momento ya está cerrada.
793
+
El `inventory.add` que sigue a `fetch``(*)` falla con el error "transacción inactiva", porque la transacción se autocompletó y, llegado ese momento, ya está cerrada.
794
794
795
795
La forma de sortear esto es la misma que con el IndexedDB nativo: Hacer una nueva transacción o simplemente partir las cosas.
796
796
1. Preparar los datos y buscar todo lo que sea necesario primero.
@@ -805,7 +805,7 @@ Esto funciona bien la mayor parte del tiempo. Los ejemplos están en la página
805
805
En algunos raros casos necesitamos el objeto `request` original. Podemos accederlo con la propiedad `promise.request` de la promesa:
806
806
807
807
```js
808
-
let promise =books.add(book); //obtener una promesa (no espera por su resultado)
808
+
let promise =books.add(book); //obtiene una promesa (no espera por su resultado)
809
809
810
810
let request =promise.request; // objeto request nativo
811
811
let transaction =request.transaction; // objeto transaction nativo
@@ -817,7 +817,7 @@ let result = await promise; // si aún se necesita
817
817
818
818
## Resumen
819
819
820
-
IndexedDB puede ser pensado como "localStorage con esteroides". Es una simple base de datos de clave-valor, suficientemente poderosa para apps fuera de línea y fácil de usar.
820
+
IndexedDB puede considerarse como "localStorage con esteroides". Es una simple base de datos de clave-valor, suficientemente poderosa para apps fuera de línea y fácil de usar.
821
821
822
822
El mejor manual es la especificación, [la actual](https://www.w3.org/TR/IndexedDB-2/) es 2.0, pero algunos métodos de [3.0](https://w3c.github.io/IndexedDB/) (no muy diferente) están soportados parcialmente.
0 commit comments