Una guía rápida pero completa para IndexedDB y el almacenamiento de datos en navegadores

¿Interesado en aprender JavaScript? Obtenga mi libro electrónico de JavaScript en jshandbook.com

Introducción a IndexedDB

IndexedDB es una de las capacidades de almacenamiento introducidas en los navegadores a lo largo de los años.

Es un almacén de clave / valor (una base de datos noSQL) que se considera la solución definitiva para almacenar datos en navegadores .

Es una API asincrónica, lo que significa que la realización de operaciones costosas no bloqueará el hilo de la interfaz de usuario, proporcionando una experiencia descuidada a los usuarios. Puede almacenar una cantidad indefinida de datos, aunque una vez que se sobrepasa un cierto umbral, se le pide al usuario que le dé al sitio límites más altos.

Es compatible con todos los navegadores modernos.

Es compatible con transacciones, control de versiones y ofrece un buen rendimiento.

Dentro del navegador también podemos utilizar:

  • Cookies : pueden albergar una cantidad muy pequeña de cadenas
  • Almacenamiento web (o almacenamiento DOM), un término que comúnmente identifica localStorage y sessionStorage, dos almacenes de clave / valor. sessionStorage, no retiene datos, que se borran cuando finaliza la sesión, mientras que localStorage mantiene los datos entre sesiones

El almacenamiento local / de sesión tiene la desventaja de tener un límite de tamaño pequeño (e inconsistente), con la implementación de los navegadores que ofrecen de 2 MB a 10 MB de espacio por sitio.

En el pasado también teníamos Web SQL , una envoltura alrededor de SQLite, pero ahora esto está obsoleto y no es compatible con algunos navegadores modernos, nunca ha sido un estándar reconocido y por lo tanto no debería usarse, aunque el 83% de los usuarios tienen esta tecnología en su dispositivos de acuerdo con Can I Use.

Si bien técnicamente puede crear varias bases de datos por sitio, generalmente crea una sola base de datos , y dentro de esa base de datos puede crear múltiples almacenes de objetos .

Una base de datos es privada para un dominio , por lo que ningún otro sitio puede acceder a otras tiendas de IndexedDB.

Cada tienda generalmente contiene un conjunto de cosas , que pueden ser

  • instrumentos de cuerda
  • números
  • objetos
  • matrices
  • fechas

Por ejemplo, puede tener una tienda que contenga publicaciones, otra que contenga comentarios.

Una tienda contiene una serie de artículos que tienen una clave única, que representa la forma en que se puede identificar un objeto.

Puede modificar esas tiendas mediante transacciones, realizando operaciones de agregar, editar y eliminar, e iterar sobre los artículos que contienen.

Desde el advenimiento de Promises en ES6 y el posterior cambio de las API al uso de promesas, la API IndexedDB parece un poco anticuada .

Si bien no hay nada de malo en ello, en todos los ejemplos que explicaré usaré la biblioteca prometida IndexedDB de Jake Archibald, que es una capa diminuta encima de la API IndexedDB para que sea más fácil de usar.

Esta biblioteca también se utiliza en todos los ejemplos en el sitio web de Google Developers con respecto a IndexedDB

Crear una base de datos IndexedDB

La forma más sencilla es usar unkg , agregando esto al encabezado de la página:

 import { openDB, deleteDB } from '//unpkg.com/idb?module'  

Antes de usar la API IndexedDB, asegúrese siempre de verificar la compatibilidad en el navegador, aunque está ampliamente disponible, nunca se sabe qué navegador está usando el usuario:

(() => { 'use strict' if (!('indexedDB' in window)) { console.warn('IndexedDB not supported') return } //...IndexedDB code })() 

Cómo crear una base de datos

Usando openDB():

(async () => { //... const dbName = 'mydbname' const storeName = 'store1' const version = 1 //versions start at 1 const db = await openDB(dbName, version, { upgrade(db, oldVersion, newVersion, transaction) { const store = db.createObjectStore(storeName) } }) })() 

Los primeros 2 parámetros son el nombre de la base de datos y la versión. El tercer parámetro, que es opcional, es un objeto que contiene una función llamada solo si el número de versión es mayor que la versión actual de la base de datos instalada . En el cuerpo de la función puede actualizar la estructura (almacenes e índices) del archivo db.

Agregar datos a una tienda

Agregar datos cuando se crea la tienda, inicializarla

Utiliza el putmétodo del almacén de objetos, pero primero necesitamos una referencia a él, que podemos obtener db.createObjectStore()cuando lo creamos.

Cuando se usa put, el valor es el primer argumento, la clave es el segundo. Esto se debe a que si especifica keyPathal crear el almacén de objetos, no necesita ingresar el nombre de la clave en cada solicitud de put (), solo puede escribir el valor.

Esto se llena store0tan pronto como lo creamos:

(async () => { //... const dbName = 'mydbname' const storeName = 'store0' const version = 1 const db = await openDB(dbName, version,{ upgrade(db, oldVersion, newVersion, transaction) { const store = db.createObjectStore(storeName) store.put('Hello world!', 'Hello') } }) })() 

Agregar datos cuando la tienda ya está creada, usando transacciones

Para agregar elementos más adelante, debe crear una transacción de lectura / escritura que garantice la integridad de la base de datos (si una operación falla, todas las operaciones de la transacción se revierten y el estado vuelve a un estado conocido).

Para eso, use una referencia al dbPromiseobjeto que obtuvimos al llamar openDBy ejecute:

(async () => { //... const dbName = 'mydbname' const storeName = 'store0' const version = 1 const db = await openDB(/* ... */) const tx = db.transaction(storeName, 'readwrite') const store = await tx.objectStore(storeName) const val = 'hey!' const key = 'Hello again' const value = await store.put(val, key) await tx.done })() 

Obtener datos de una tienda

Obtener un artículo de una tienda: get()

const key = 'Hello again' const item = await db.transaction(storeName).objectStore(storeName).get(key) 

Obtener todos los artículos de una tienda: getAll()

Obtener todas las claves almacenadas

const items = await db.transaction(storeName).objectStore(storeName).getAllKeys() 

Obtenga todos los valores almacenados

const items = await db.transaction(storeName).objectStore(storeName).getAll() 

Eliminando datos de IndexedDB

Eliminar la base de datos, un almacén de objetos y datos

Eliminar una base de datos IndexedDB completa

const dbName = 'mydbname' await deleteDB(dbName) 

Para eliminar datos en un almacén de objetos

Usamos una transacción:

(async () => { //... const dbName = 'mydbname' const storeName = 'store1' const version = 1 const db = await openDB(dbName, version, { upgrade(db, oldVersion, newVersion, transaction) { const store = db.createObjectStore(storeName) } }) const tx = await db.transaction(storeName, 'readwrite') const store = await tx.objectStore(storeName) const key = 'Hello again' await store.delete(key) await tx.done })() 

Migrar desde la versión anterior de una base de datos

El tercer parámetro (opcional) de la openDB()función es un objeto que puede contener una upgradefunción llamada solo si el número de versión es mayor que la versión actual de la base de datos instalada . En ese cuerpo de función puede actualizar la estructura (tiendas e índices) de la base de datos:

const name = 'mydbname' const version = 1 openDB(name, version, { upgrade(db, oldVersion, newVersion, transaction) { console.log(oldVersion) } }) 

En esta devolución de llamada, puede verificar desde qué versión está actualizando el usuario y realizar algunas operaciones en consecuencia.

Puede realizar una migración desde una versión anterior de la base de datos utilizando esta sintaxis

(async () => { //... const dbName = 'mydbname' const storeName = 'store0' const version = 1 const db = await openDB(dbName, version, { upgrade(db, oldVersion, newVersion, transaction) { switch (oldVersion) { case 0: // no db created before // a store introduced in version 1 db.createObjectStore('store1') case 1: // a new store in version 2 db.createObjectStore('store2', { keyPath: 'name' }) } db.createObjectStore(storeName) } }) })() 

Llaves únicas

createObjectStore()como puede ver en case 1acepta un segundo parámetro que indica la clave de índice de la base de datos. Esto es muy útil cuando almacena objetos: las put()llamadas no necesitan un segundo parámetro, pero pueden simplemente tomar el valor (un objeto) y la clave se asignará a la propiedad del objeto que tiene ese nombre.

El índice le brinda una forma de recuperar un valor más tarde mediante esa clave específica, y debe ser único (cada elemento debe tener una clave diferente)

Se puede configurar una clave para que aumente automáticamente, por lo que no es necesario realizar un seguimiento en el código del cliente:

db.createObjectStore('notes', { autoIncrement: true }) 

Utilice el incremento automático si sus valores no contienen una clave única (por ejemplo, si recopila direcciones de correo electrónico sin un nombre asociado).

Comprueba si existe una tienda

Puede verificar si ya existe un almacén de objetos llamando al objectStoreNames()método:

const storeName = 'store1' if (!db.objectStoreNames.contains(storeName)) { db.createObjectStore(storeName) } 

Eliminando de IndexedDB

Eliminar la base de datos, un almacén de objetos y datos

Eliminar una base de datos

await deleteDB('mydb') 

Eliminar un almacén de objetos

Un almacén de objetos solo se puede eliminar en la devolución de llamada al abrir una base de datos, y esa devolución de llamada solo se llama si especifica una versión superior a la que está instalada actualmente:

const db = await openDB('dogsdb', 2, { upgrade(db, oldVersion, newVersion, transaction) { switch (oldVersion) { case 0: // no db created before // a store introduced in version 1 db.createObjectStore('store1') case 1: // delete the old store in version 2, create a new one db.deleteObjectStore('store1') db.createObjectStore('store2') } } }) 

Para eliminar datos en una tienda de objetos, use una transacción

const key = 232 //a random key const db = await openDB(/*...*/) const tx = await db.transaction('store', 'readwrite') const store = await tx.objectStore('store') await store.delete(key) await tx.complete 

¡Hay más!

Estos son solo los conceptos básicos. No hablé de cursores y cosas más avanzadas. Hay más en IndexedDB, pero espero que esto le dé una ventaja.

¿Interesado en aprender JavaScript? Obtenga mi libro de JavaScript en jshandbook.com