// apis · Web Platform Advent #23
Was ist IndexedDB? Die Browser-Datenbank erklärt
IndexedDB ist die echte Datenbank des Browsers: asynchron, transaktional und weit größer als localStorage. Wie onupgradeneeded funktioniert, warum Transaktionen unter Ihnen zugehen, wozu Indizes da sind und wann Sie sie statt Web Storage nehmen.
IndexedDB ist die echte Datenbank des Browsers. Wo Web Storage Ihnen eine synchrone Kiste voller Zeichenketten mit rund 5 MB Deckel gibt, bietet IndexedDB einen asynchronen, transaktionalen Speicher für strukturierte Daten, dimensioniert in Hunderten von Megabyte und mehr. Es ist kein SQL. Es ist ein Schlüssel-Wert-Speicher mit Indizes, und sobald das sitzt, ergibt sich der Rest der API von selbst.
Eine Datenbank öffnen, und die einzige Stelle, an der man ihre Form ändert
Man öffnet eine Datenbank über Name und Versionsnummer. Ist die angeforderte Version höher als die auf der Platte, löst der Browser upgradeneeded aus, und dieser Handler ist die einzige Stelle, an der Sie Object Stores und Indizes anlegen oder löschen dürfen.
const request = indexedDB.open('notes-db', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const store = db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
store.createIndex('by_tag', 'tag', { unique: false });
};
request.onsuccess = (event) => {
const db = event.target.result;
};
request.onerror = () => console.error(request.error); Das überrascht, weil es die gewohnte Praxis umdreht. Sie können einen Store nicht faul beim ersten Bedarf anlegen. Das Schema lebt in einer einzigen Funktion, gebunden an eine Versionsnummer, die Sie erhöhen, wenn sich die Struktur ändert.
Alles ist ein Request, und Requests sind asynchron
Fast jeder IndexedDB-Aufruf liefert ein IDBRequest statt eines Werts. Das Ergebnis lesen Sie in onsuccess, den Fehlerfall behandeln Sie in onerror. Nichts blockiert den Haupt-Thread, und genau das ist der Grund, sie localStorage vorzuziehen, sobald es um Menge geht.
Lesen und Schreiben geschehen in einer Transaktion
Einen Store fasst man nicht direkt an. Man öffnet eine Transaktion über einen oder mehrere Stores, im Modus readonly oder readwrite, und arbeitet durch sie hindurch.
const tx = db.transaction('notes', 'readwrite');
const store = tx.objectStore('notes');
store.add({ title: 'Buy milk', tag: 'errand', done: false });
tx.oncomplete = () => console.log('written');
tx.onerror = () => console.error(tx.error); Hier ist die Falle, die man vorher kennen sollte. Eine Transaktion committet automatisch, sobald für sie keine Arbeit mehr aussteht. Warten Sie mittendrin auf etwas Fremdes, etwa einen fetch, schließt sich die Transaktion während des Wartens, und der nächste Aufruf wirft TransactionInactiveError. Erledigen Sie die äußere Arbeit zuerst und öffnen Sie die Transaktion danach.
Indizes, die den Namen erklären
Standardmäßig holt man Datensätze über ihren Primärschlüssel. Ein Index ist eine zweite Ordnung derselben Datensätze, mit der Sie über eine andere Eigenschaft abfragen können.
const tx = db.transaction('notes', 'readonly');
const index = tx.objectStore('notes').index('by_tag');
const req = index.getAll('errand');
req.onsuccess = () => console.log(req.result); Für Bereiche statt exakter Treffer kombinieren Sie einen Index mit IDBKeyRange und einem Cursor, der die Ergebnisse einzeln durchläuft, statt alle in den Speicher zu laden.
Was hineinpasst, und wie viel
IndexedDB nimmt alles auf, was der Structured-Clone-Algorithmus beherrscht: Objekte, Arrays, Date, Blob, File, ArrayBuffer. Funktionen und DOM-Knoten sind nicht klonbar und werfen einen Fehler. Ein Umweg über JSON.stringify entfällt, was auch heißt, dass Zahlen Zahlen bleiben und Daten Daten.
Das Kontingent ist ein Anteil des verfügbaren Speicherplatzes und keine feste 5-MB-Grenze, also der richtige Ort für zwischengespeicherte API-Antworten, Offline-Dokumente oder Medien. Dieser Speicher ist standardmäßig best effort und kann unter Speicherdruck geräumt werden; rufen Sie navigator.storage.persist() auf, wenn die Daten das überleben müssen, und navigator.storage.estimate(), um Ihren Verbrauch zu sehen.
IndexedDB oder localStorage?
| localStorage | IndexedDB | |
|---|---|---|
| API-Stil | Synchron, blockiert den Haupt-Thread | Asynchron, ereignisbasiert |
| Werte | Nur Zeichenketten | Strukturiert klonbare Werte |
| Grobe Größe | Etwa 5 MB je Origin | Ein Anteil des freien Speicherplatzes |
| Abfragen | Nur über den Schlüssel | Über Schlüssel, Index, Bereich mit Cursor |
| Gut für | Theme, Flags, kleine Einstellungen | Offline-Daten, Caches, Dateien, große Listen |
Beide sind auf die Origin beschränkt, und keines ist ein Ort für Geheimnisse: jedes Skript auf der Seite kann sie lesen.
Lohnt ein Wrapper?
Die rohe API ist wortreich, weil sie älter ist als Promises. Kleine Bibliotheken verpacken Requests in Promises und nehmen den Großteil des Boilerplates weg; für Anwendungscode ist das eine vernünftige Vorgabe. Lernen Sie trotzdem zuerst das Modell darunter: upgradeneeded, Transaktionen und ihr automatischer Commit verhalten sich darunter gleich, und jeder verwirrende Fehler, den Sie treffen werden, kommt aus diesen drei Punkten, nicht aus dem Wrapper.
Greifen Sie zu IndexedDB, wenn Daten strukturiert, groß oder offline verfügbar sein müssen, oft zusammen mit einem Service Worker. Behalten Sie localStorage für die Handvoll kleiner Flags, bei denen ein synchroner Lesezugriff wirklich einfacher ist.