In diesem Leitfaden werden die Grundlagen der IndexedDB API behandelt.
Wir verwenden die Bibliothek IndexedDB Promised von Jake Archibald, die der IndexedDB API sehr ähnlich ist, aber Promises verwendet, die Sie für eine prägnantere Syntax await können. Dadurch wird die API vereinfacht, während ihre Struktur beibehalten wird.
Was ist IndexedDB?
IndexedDB ist ein umfangreiches NoSQL-Speichersystem, mit dem sich fast alles im Browser des Nutzers speichern lässt. Zusätzlich zu den üblichen Such-, Abruf- und Einfügeaktionen unterstützt IndexedDB auch Transaktionen und eignet sich gut zum Speichern großer Mengen strukturierter Daten.
Jede IndexedDB-Datenbank ist einem Ursprung (in der Regel der Domain oder Subdomain der Website) zugeordnet. Das bedeutet, dass sie nicht auf andere Ursprünge zugreifen kann und auch nicht von anderen Ursprüngen aus auf sie zugegriffen werden kann. Die Limits für die Datenspeicherung sind in der Regel groß, sofern sie überhaupt vorhanden sind. Die verschiedenen Browser gehen jedoch unterschiedlich mit Limits und der Datenbereinigung um. Weitere Informationen finden Sie im Abschnitt Weiterführende Informationen.
IndexedDB-Begriffe
- Datenbank
- Die höchste Ebene von IndexedDB. Er enthält die Objektspeicher, die wiederum die Daten enthalten, die Sie persistent speichern möchten. Sie können mehrere Datenbanken mit beliebigen Namen erstellen.
- Objektspeicher
- Ein einzelner Bucket zum Speichern von Daten, ähnlich wie Tabellen in relationalen Datenbanken.
In der Regel gibt es für jeden Typ (kein JavaScript-Datentyp) von Daten, die Sie speichern, einen Objektspeicher. Im Gegensatz zu Datenbanktabellen müssen die JavaScript-Datentypen der Daten in einem Speicher nicht konsistent sein. Wenn eine App beispielsweise einen Objektspeicher
peoplemit Informationen zu drei Personen hat, könnten die Altersattribute dieser Personen53,'twenty-five'undunknownsein. - Index
- Eine Art von Objektspeicher, mit dem Daten in einem anderen Objektspeicher (dem Referenzobjektspeicher) nach einem einzelnen Attribut der Daten organisiert werden. Der Index wird verwendet, um Datensätze im Objektspeicher anhand dieses Attributs abzurufen. Wenn Sie beispielsweise Personen speichern, möchten Sie sie später möglicherweise nach Name, Alter oder Lieblingstier abrufen.
- Vorgang
- Eine Interaktion mit der Datenbank.
- Transaktion
- Ein Wrapper für einen Vorgang oder eine Gruppe von Vorgängen, der die Datenbankintegrität gewährleistet. Wenn eine der Aktionen in einer Transaktion fehlschlägt, wird keine davon angewendet und die Datenbank kehrt in den Zustand zurück, in dem sie sich vor Beginn der Transaktion befand. Alle Lese- oder Schreibvorgänge in IndexedDB müssen Teil einer Transaktion sein. Dies ermöglicht atomare Lese-Änderungs-Schreibvorgänge ohne das Risiko von Konflikten mit anderen Threads, die gleichzeitig auf die Datenbank zugreifen.
- Cursor
- Ein Mechanismus zum Iterieren über mehrere Datensätze in einer Datenbank.
Unterstützung für IndexedDB prüfen
IndexedDB wird fast universell unterstützt.
Wenn Sie jedoch mit älteren Browsern arbeiten, ist es keine schlechte Idee, die Unterstützung für die Funktion zu prüfen. Am einfachsten ist es, das window-Objekt zu prüfen:
function indexedDBStuff () {
// Check for IndexedDB support:
if (!('indexedDB' in window)) {
// Can't use IndexedDB
console.log("This browser doesn't support IndexedDB");
return;
} else {
// Do IndexedDB stuff here:
// ...
}
}
// Run IndexedDB code:
indexedDBStuff();
Datenbank öffnen
Mit IndexedDB können Sie mehrere Datenbanken mit beliebigen Namen erstellen. Wenn eine Datenbank nicht vorhanden ist, wenn Sie versuchen, sie zu öffnen, wird sie automatisch erstellt.
Verwenden Sie zum Öffnen einer Datenbank die Methode openDB() aus der Bibliothek idb:
import {openDB} from 'idb';
async function useDB () {
// Returns a promise, which makes `idb` usable with async-await.
const dbPromise = await openDB('example-database', version, events);
}
useDB();
Diese Methode gibt ein Promise zurück, das in ein Datenbankobjekt aufgelöst wird. Wenn Sie die Methode openDB() verwenden, geben Sie einen Namen, eine Versionsnummer und ein Ereignisobjekt an, um die Datenbank einzurichten.
Hier ein Beispiel für die Methode openDB() im Kontext:
import {openDB} from 'idb';
async function useDB () {
// Opens the first version of the 'test-db1' database.
// If the database does not exist, it will be created.
const dbPromise = await openDB('test-db1', 1);
}
useDB();
Platzieren Sie die Prüfung auf IndexedDB-Unterstützung am Anfang der anonymen Funktion. Dadurch wird die Funktion beendet, wenn der Browser IndexedDB nicht unterstützt. Wenn die Funktion fortgesetzt werden kann, ruft sie die Methode openDB() auf, um eine Datenbank mit dem Namen 'test-db1' zu öffnen.
In diesem Beispiel wurde das optionale Ereignisobjekt weggelassen, um die Dinge einfach zu halten. Sie müssen es jedoch angeben, um mit IndexedDB sinnvoll arbeiten zu können.
Mit Objektspeichern arbeiten
Eine IndexedDB-Datenbank enthält einen oder mehrere Objektspeicher, die jeweils eine Spalte für einen Schlüssel und eine weitere Spalte für die mit diesem Schlüssel verknüpften Daten haben.
Objektspeicher erstellen
Eine gut strukturierte IndexedDB-Datenbank sollte für jeden Datentyp, der beibehalten werden muss, einen Objektspeicher haben. Eine Website, auf der Nutzerprofile und Notizen gespeichert werden, könnte beispielsweise einen Objektspeicher people mit person-Objekten und einen Objektspeicher notes mit note-Objekten haben.
Um die Datenbankintegrität zu gewährleisten, können Sie Objektspeicher im Ereignisobjekt nur in einem openDB()-Aufruf erstellen oder entfernen. Das Ereignisobjekt stellt eine upgrade()-Methode bereit, mit der Sie Objektspeicher erstellen können. Rufen Sie die Methode createObjectStore() innerhalb der Methode upgrade() auf, um den Objektspeicher zu erstellen:
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('example-database', 1, {
upgrade (db) {
// Creates an object store:
db.createObjectStore('storeName', options);
}
});
}
createStoreInDB();
Diese Methode verwendet den Namen des Objektspeichers und ein optionales Konfigurationsobjekt, mit dem Sie verschiedene Attribute für den Objektspeicher definieren können.
Im Folgenden finden Sie ein Beispiel für die Verwendung von createObjectStore():
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('test-db1', 1, {
upgrade (db) {
console.log('Creating a new object store...');
// Checks if the object store exists:
if (!db.objectStoreNames.contains('people')) {
// If the object store does not exist, create it:
db.createObjectStore('people');
}
}
});
}
createStoreInDB();
In diesem Beispiel wird ein Ereignisobjekt an die Methode openDB() übergeben, um den Objektspeicher zu erstellen. Wie zuvor wird der Objektspeicher in der Methode upgrade() des Ereignisobjekts erstellt. Da der Browser jedoch einen Fehler ausgibt, wenn Sie versuchen, einen bereits vorhandenen Objektspeicher zu erstellen, empfehlen wir, die Methode createObjectStore() in eine if-Anweisung einzubinden, die prüft, ob der Objektspeicher vorhanden ist. Rufen Sie im Block if die Funktion createObjectStore() auf, um einen Objektspeicher mit dem Namen 'firstOS' zu erstellen.
Primärschlüssel definieren
Wenn Sie Objektspeicher definieren, können Sie festlegen, wie Daten im Speicher mithilfe eines Primärschlüssels eindeutig identifiziert werden. Sie können einen Primärschlüssel entweder durch Definieren eines Schlüsselpfads oder mithilfe eines Schlüsselgenerators definieren.
Ein Schlüsselpfad ist eine Property, die immer vorhanden ist und einen eindeutigen Wert enthält. Bei einem people-Objektspeicher können Sie beispielsweise die E-Mail-Adresse als Schlüsselpfad auswählen:
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('test-db2', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('people')) {
db.createObjectStore('people', { keyPath: 'email' });
}
}
});
}
createStoreInDB();
In diesem Beispiel wird ein Objektspeicher mit dem Namen 'people' erstellt und das Attribut email als Primärschlüssel in der Option keyPath zugewiesen.
Sie können auch einen Schlüsselgenerator wie autoIncrement verwenden. Der Schlüsselgenerator erstellt für jedes Objekt, das dem Objektspeicher hinzugefügt wird, einen eindeutigen Wert. Wenn Sie keinen Schlüssel angeben, erstellt IndexedDB standardmäßig einen Schlüssel und speichert ihn separat von den Daten.
Im folgenden Beispiel wird ein Objektspeicher mit dem Namen 'notes' erstellt und der Primärschlüssel so festgelegt, dass er automatisch als automatisch inkrementierte Zahl zugewiesen wird:
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('test-db2', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('notes')) {
db.createObjectStore('notes', { autoIncrement: true });
}
}
});
}
createStoreInDB();
Das folgende Beispiel ähnelt dem vorherigen Beispiel, aber dieses Mal wird der automatisch inkrementierte Wert explizit einem Attribut mit dem Namen 'id' zugewiesen.
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('test-db2', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('logs')) {
db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
}
}
});
}
createStoreInDB();
Welche Methode Sie zum Definieren des Schlüssels verwenden, hängt von Ihren Daten ab. Wenn Ihre Daten ein Attribut haben, das immer eindeutig ist, können Sie es als keyPath festlegen, um diese Eindeutigkeit zu erzwingen. Verwenden Sie andernfalls einen automatisch inkrementierten Wert.
Mit dem folgenden Code werden drei Objektspeicher erstellt, um die verschiedenen Möglichkeiten zum Definieren von Primärschlüsseln in Objektspeichern zu veranschaulichen:
import {openDB} from 'idb';
async function createStoresInDB () {
const dbPromise = await openDB('test-db2', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('people')) {
db.createObjectStore('people', { keyPath: 'email' });
}
if (!db.objectStoreNames.contains('notes')) {
db.createObjectStore('notes', { autoIncrement: true });
}
if (!db.objectStoreNames.contains('logs')) {
db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
}
}
});
}
createStoresInDB();
Indexe definieren
Indexe sind eine Art Objektspeicher, mit dem Daten aus dem Referenzobjektspeicher anhand eines bestimmten Attributs abgerufen werden. Ein Index befindet sich im Referenzobjektspeicher und enthält dieselben Daten, verwendet jedoch das angegebene Attribut als Schlüsselpfad anstelle des Primärschlüssels des Referenzspeichers. Indexe müssen beim Erstellen der Objektspeicher erstellt werden und können verwendet werden, um eine eindeutige Einschränkung für Ihre Daten zu definieren.
Wenn Sie einen Index erstellen möchten, rufen Sie die Methode createIndex() für eine Objektspeicherinstanz auf:
import {openDB} from 'idb';
async function createIndexInStore() {
const dbPromise = await openDB('storeName', 1, {
upgrade (db) {
const objectStore = db.createObjectStore('storeName');
objectStore.createIndex('indexName', 'property', options);
}
});
}
createIndexInStore();
Mit dieser Methode wird ein Indexobjekt erstellt und zurückgegeben. Die Methode createIndex() für die Instanz des Objektspeichers verwendet den Namen des neuen Index als erstes Argument. Das zweite Argument bezieht sich auf das Attribut der Daten, die Sie indexieren möchten. Mit dem letzten Argument können Sie zwei Optionen definieren, die bestimmen, wie der Index funktioniert: unique und multiEntry. Wenn unique auf true gesetzt ist, lässt der Index keine doppelten Werte für einen einzelnen Schlüssel zu. Als Nächstes bestimmt multiEntry, wie sich createIndex() verhält, wenn das indexierte Attribut ein Array ist. Wenn der Wert auf true gesetzt ist, fügt createIndex() für jedes Arrayelement einen Eintrag im Index hinzu. Andernfalls wird ein einzelner Eintrag mit dem Array hinzugefügt.
Beispiel:
import {openDB} from 'idb';
async function createIndexesInStores () {
const dbPromise = await openDB('test-db3', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('people')) {
const peopleObjectStore = db.createObjectStore('people', { keyPath: 'email' });
peopleObjectStore.createIndex('gender', 'gender', { unique: false });
peopleObjectStore.createIndex('ssn', 'ssn', { unique: true });
}
if (!db.objectStoreNames.contains('notes')) {
const notesObjectStore = db.createObjectStore('notes', { autoIncrement: true });
notesObjectStore.createIndex('title', 'title', { unique: false });
}
if (!db.objectStoreNames.contains('logs')) {
const logsObjectStore = db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
}
}
});
}
createIndexesInStores();
In diesem Beispiel haben die Objektspeicher 'people' und 'notes' Indexe. Wenn Sie die Indexe erstellen möchten, weisen Sie zuerst das Ergebnis von createObjectStore() (ein Objektspeicherobjekt) einer Variablen zu, damit Sie createIndex() dafür aufrufen können.
Mit Daten arbeiten
In diesem Abschnitt wird beschrieben, wie Sie Daten erstellen, lesen, aktualisieren und löschen. Diese Vorgänge sind alle asynchron und verwenden Promises, während die IndexedDB API Anfragen verwendet. Das vereinfacht die API. Anstatt auf Ereignisse zu warten, die durch die Anfrage ausgelöst werden, können Sie .then() für das Datenbankobjekt aufrufen, das von der Methode openDB() zurückgegeben wird, um Interaktionen mit der Datenbank zu starten oder die Erstellung zu await.
Alle Datenvorgänge in IndexedDB werden innerhalb einer Transaktion ausgeführt. Jeder Vorgang hat das folgende Format:
- Datenbankobjekt abrufen.
- Offene Transaktion in der Datenbank.
- Objektspeicher für Transaktion öffnen.
- Vorgang für Objektspeicher ausführen.
Eine Transaktion kann als sicherer Wrapper für einen Vorgang oder eine Gruppe von Vorgängen betrachtet werden. Wenn eine der Aktionen innerhalb einer Transaktion fehlschlägt, werden alle Aktionen rückgängig gemacht. Transaktionen sind spezifisch für einen oder mehrere Objektspeicher, die Sie beim Öffnen der Transaktion definieren. Sie können schreibgeschützt sein oder Lese- und Schreibzugriff haben. Dies gibt an, ob die Vorgänge innerhalb der Transaktion die Daten lesen oder eine Änderung an der Datenbank vornehmen.
Daten erstellen
Wenn Sie Daten erstellen möchten, rufen Sie die Methode add() für die Datenbankinstanz auf und übergeben Sie die Daten, die Sie hinzufügen möchten. Das erste Argument der Methode add() ist der Objektspeicher, dem Sie die Daten hinzufügen möchten. Das zweite Argument ist ein Objekt, das die Felder und zugehörigen Daten enthält, die Sie hinzufügen möchten. Hier ist das einfachste Beispiel, bei dem eine einzelne Datenzeile hinzugefügt wird:
import {openDB} from 'idb';
async function addItemToStore () {
const db = await openDB('example-database', 1);
await db.add('storeName', {
field: 'data'
});
}
addItemToStore();
Jeder add()-Aufruf erfolgt innerhalb einer Transaktion. Selbst wenn das Promise erfolgreich aufgelöst wird, bedeutet das nicht unbedingt, dass der Vorgang funktioniert hat. Um sicherzugehen, dass der Vorgang „add“ ausgeführt wurde, müssen Sie mit der Methode transaction.done() prüfen, ob die gesamte Transaktion abgeschlossen wurde. Dies ist ein Promise, das aufgelöst wird, wenn die Transaktion abgeschlossen ist, und abgelehnt wird, wenn die Transaktion fehlerhaft ist. Sie müssen diese Prüfung für alle Schreibvorgänge durchführen, da Sie nur so feststellen können, ob die Änderungen an der Datenbank tatsächlich vorgenommen wurden.
Der folgende Code zeigt die Verwendung der Methode add() in einer Transaktion:
import {openDB} from 'idb';
async function addItemsToStore () {
const db = await openDB('test-db4', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('foods')) {
db.createObjectStore('foods', { keyPath: 'name' });
}
}
});
// Create a transaction on the 'foods' store in read/write mode:
const tx = db.transaction('foods', 'readwrite');
// Add multiple items to the 'foods' store in a single transaction:
await Promise.all([
tx.store.add({
name: 'Sandwich',
price: 4.99,
description: 'A very tasty sandwich!',
created: new Date().getTime(),
}),
tx.store.add({
name: 'Eggs',
price: 2.99,
description: 'Some nice eggs you can cook up!',
created: new Date().getTime(),
}),
tx.done
]);
}
addItemsToStore();
Nachdem Sie die Datenbank geöffnet und bei Bedarf einen Objektspeicher erstellt haben, müssen Sie eine Transaktion öffnen, indem Sie die Methode transaction() dafür aufrufen. Diese Methode verwendet ein Argument für den Store, in dem Sie die Transaktion ausführen möchten, sowie den Modus.
In diesem Fall möchten wir in den Store schreiben, daher wird in diesem Beispiel 'readwrite' angegeben.
Im nächsten Schritt werden dem Geschäft im Rahmen der Transaktion Artikel hinzugefügt.
Im vorherigen Beispiel haben wir es mit drei Vorgängen im 'foods'-Speicher zu tun, die jeweils ein Promise zurückgeben:
- Einen Datensatz für ein leckeres Sandwich hinzufügen.
- Ich füge einen Eintrag für Eier hinzu.
- Signalisieren, dass die Transaktion abgeschlossen ist (
tx.done).
Da alle diese Aktionen Promise-basiert sind, müssen wir warten, bis sie alle abgeschlossen sind. Die Übergabe dieser Promises an Promise.all ist eine gute, ergonomische Möglichkeit, dies zu erreichen. Promise.all akzeptiert ein Array von Promises und wird beendet, wenn alle an sie übergebenen Promises aufgelöst wurden.
Für die beiden hinzugefügten Datensätze ruft die store-Schnittstelle der Transaktionsinstanz add() auf und übergibt die Daten. Sie können den Aufruf Promise.all mit await versehen, damit er beendet wird, wenn die Transaktion abgeschlossen ist.
Daten lesen
Wenn Sie Daten lesen möchten, rufen Sie die Methode get() für die Datenbankinstanz auf, die Sie mit der Methode openDB() abrufen.
get() verwendet den Namen des Speichers und den Primärschlüsselwert des Objekts, das Sie abrufen möchten. Hier ein einfaches Beispiel:
import {openDB} from 'idb';
async function getItemFromStore () {
const db = await openDB('example-database', 1);
// Get a value from the object store by its primary key value:
const value = await db.get('storeName', 'unique-primary-key-value');
}
getItemFromStore();
Wie bei add() gibt die Methode get() ein Promise zurück. Sie können sie also nach Belieben mit await verwenden oder den .then()-Callback des Promise nutzen.
Im folgenden Beispiel wird die Methode get() für den Objektspeicher 'foods' der Datenbank 'test-db4' verwendet, um eine einzelne Zeile anhand des Primärschlüssels 'name' abzurufen:
import {openDB} from 'idb';
async function getItemFromStore () {
const db = await openDB('test-db4', 1);
const value = await db.get('foods', 'Sandwich');
console.dir(value);
}
getItemFromStore();
Das Abrufen einer einzelnen Zeile aus der Datenbank ist recht einfach: Öffnen Sie die Datenbank und geben Sie den Objektspeicher und den Primärschlüsselwert der Zeile an, aus der Sie Daten abrufen möchten. Da die Methode get() ein Promise zurückgibt, können Sie sie mit await aufrufen.
Daten aktualisieren
Rufen Sie zum Aktualisieren von Daten die Methode put() für den Objektspeicher auf. Die Methode put() ähnelt der Methode add() und kann auch anstelle von add() zum Erstellen von Daten verwendet werden. Hier ein einfaches Beispiel für die Verwendung von put(), um eine Zeile in einem Objektspeicher anhand ihres Primärschlüsselwerts zu aktualisieren:
import {openDB} from 'idb';
async function updateItemInStore () {
const db = await openDB('example-database', 1);
// Update a value from in an object store with an inline key:
await db.put('storeName', { inlineKeyName: 'newValue' });
// Update a value from in an object store with an out-of-line key.
// In this case, the out-of-line key value is 1, which is the
// auto-incremented value.
await db.put('otherStoreName', { field: 'value' }, 1);
}
updateItemInStore();
Wie andere Methoden gibt auch diese Methode ein Promise zurück. Sie können put() auch als Teil einer Transaktion verwenden. Hier ist ein Beispiel, in dem der Preis für das Sandwich und die Eier im zuvor verwendeten Geschäft 'foods' aktualisiert wird:
import {openDB} from 'idb';
async function updateItemsInStore () {
const db = await openDB('test-db4', 1);
// Create a transaction on the 'foods' store in read/write mode:
const tx = db.transaction('foods', 'readwrite');
// Update multiple items in the 'foods' store in a single transaction:
await Promise.all([
tx.store.put({
name: 'Sandwich',
price: 5.99,
description: 'A MORE tasty sandwich!',
updated: new Date().getTime() // This creates a new field
}),
tx.store.put({
name: 'Eggs',
price: 3.99,
description: 'Some even NICER eggs you can cook up!',
updated: new Date().getTime() // This creates a new field
}),
tx.done
]);
}
updateItemsInStore();
Wie Elemente aktualisiert werden, hängt davon ab, wie Sie einen Schlüssel festlegen. Wenn Sie einen keyPath festlegen, wird jede Zeile im Objektspeicher mit einem Inline-Schlüssel verknüpft. Im vorherigen Beispiel werden Zeilen basierend auf diesem Schlüssel aktualisiert. Wenn Sie Zeilen in dieser Situation aktualisieren, müssen Sie diesen Schlüssel angeben, um das entsprechende Element im Objektspeicher zu aktualisieren. Sie können auch einen Out-of-Line-Schlüssel erstellen, indem Sie einen autoIncrement als Primärschlüssel festlegen.
Daten löschen
Rufen Sie zum Löschen von Daten die Methode delete() für den Objektspeicher auf:
import {openDB} from 'idb';
async function deleteItemFromStore () {
const db = await openDB('example-database', 1);
// Delete a value
await db.delete('storeName', 'primary-key-value');
}
deleteItemFromStore();
Wie add() und put() können Sie dies als Teil einer Transaktion verwenden:
import {openDB} from 'idb';
async function deleteItemsFromStore () {
const db = await openDB('test-db4', 1);
// Create a transaction on the 'foods' store in read/write mode:
const tx = db.transaction('foods', 'readwrite');
// Delete multiple items from the 'foods' store in a single transaction:
await Promise.all([
tx.store.delete('Sandwich'),
tx.store.delete('Eggs'),
tx.done
]);
}
deleteItemsFromStore();
Die Struktur der Datenbankinteraktion ist dieselbe wie bei den anderen Vorgängen. Prüfen Sie, ob die gesamte Transaktion abgeschlossen ist, indem Sie die Methode tx.done in das Array aufnehmen, das Sie an Promise.all übergeben.
Alle Daten abrufen
Bisher haben Sie Objekte nur einzeln aus dem Store abgerufen. Sie können auch alle Daten oder eine Teilmenge aus einem Objektspeicher oder Index mit der Methode getAll() oder mit Cursorn abrufen.
Die Methode getAll()
Die einfachste Möglichkeit, alle Daten eines Objektspeichers abzurufen, besteht darin, getAll() für den Objektspeicher oder Index aufzurufen, z. B. so:
import {openDB} from 'idb';
async function getAllItemsFromStore () {
const db = await openDB('test-db4', 1);
// Get all values from the designated object store:
const allValues = await db.getAll('storeName');
console.dir(allValues);
}
getAllItemsFromStore();
Diese Methode gibt alle Objekte im Objektspeicher ohne Einschränkungen zurück. Dies ist die direkteste Methode, um alle Werte aus einem Objektspeicher abzurufen, aber auch die am wenigsten flexible.
import {openDB} from 'idb';
async function getAllItemsFromStore () {
const db = await openDB('test-db4', 1);
// Get all values from the designated object store:
const allValues = await db.getAll('foods');
console.dir(allValues);
}
getAllItemsFromStore();
In diesem Beispiel wird getAll() im Objektspeicher 'foods' aufgerufen. Dadurch werden alle Objekte aus 'foods' zurückgegeben, sortiert nach dem Primärschlüssel.
Cursor verwenden
Cursor sind eine flexiblere Methode, um mehrere Objekte abzurufen. Ein Cursor wählt jedes Objekt in einem Objektspeicher oder Index einzeln aus, sodass Sie etwas mit den Daten tun können, wenn sie ausgewählt sind. Cursor funktionieren wie die anderen Datenbankvorgänge in Transaktionen.
Wenn Sie einen Cursor erstellen möchten, rufen Sie im Rahmen einer Transaktion openCursor() für den Objektspeicher auf. Im folgenden Beispiel wird der 'foods'-Speicher aus den vorherigen Beispielen verwendet, um einen Cursor durch alle Datenzeilen in einem Objektspeicher zu bewegen:
import {openDB} from 'idb';
async function getAllItemsFromStoreWithCursor () {
const db = await openDB('test-db4', 1);
const tx = await db.transaction('foods', 'readonly');
// Open a cursor on the designated object store:
let cursor = await tx.store.openCursor();
// Iterate on the cursor, row by row:
while (cursor) {
// Show the data in the row at the current cursor position:
console.log(cursor.key, cursor.value);
// Advance the cursor to the next row:
cursor = await cursor.continue();
}
}
getAllItemsFromStoreWithCursor();
Die Transaktion wird in diesem Fall im 'readonly'-Modus geöffnet und die Methode openCursor wird aufgerufen. In einer nachfolgenden while-Schleife können die Eigenschaften key und value der Zeile an der aktuellen Position des Cursors gelesen werden. Sie können diese Werte dann so bearbeiten, wie es für Ihre App am sinnvollsten ist. Wenn Sie bereit sind, können Sie die Methode continue() des cursor-Objekts aufrufen, um zur nächsten Zeile zu wechseln. Die while-Schleife wird beendet, wenn der Cursor das Ende des Datasets erreicht.
Cursor mit Bereichen und Indexen verwenden
Mit Indexen können Sie die Daten in einem Objektspeicher anhand eines anderen Attributs als des Primärschlüssels abrufen. Sie können einen Index für jede Property erstellen, die dann zum keyPath für den Index wird. Geben Sie einen Bereich für diese Property an und rufen Sie die Daten innerhalb des Bereichs mit getAll() oder einem Cursor ab.
Definieren Sie den Bereich mit dem IDBKeyRange-Objekt und einer der folgenden Methoden:
upperBound().lowerBound().bound()(beides).only().includes().
Mit den Methoden upperBound() und lowerBound() werden die oberen und unteren Grenzen des Bereichs angegeben.
IDBKeyRange.lowerBound(indexKey);
Oder:
IDBKeyRange.upperBound(indexKey);
Beide verwenden ein Argument: den keyPath-Wert des Index für das Element, das Sie als Ober- oder Untergrenze angeben möchten.
Die Methode bound() gibt sowohl eine Ober- als auch eine Untergrenze an:
IDBKeyRange.bound(lowerIndexKey, upperIndexKey);
Der Bereich für diese Funktionen ist standardmäßig inklusiv. Das bedeutet, dass die Daten, die als Grenzen des Bereichs angegeben sind, enthalten sind. Wenn Sie diese Werte ausschließen möchten, geben Sie den Bereich als exklusiv an. Übergeben Sie dazu true als zweites Argument für lowerBound() oder upperBound() oder als drittes und viertes Argument von bound() für die Unter- bzw. Obergrenze.
Im nächsten Beispiel wird ein Index für das Attribut 'price' im Objektspeicher 'foods' verwendet. Der Store hat jetzt auch ein Formular mit zwei Eingaben für die Ober- und Untergrenze des Bereichs. Verwenden Sie den folgenden Code, um Lebensmittel mit Preisen zwischen diesen Limits zu finden:
import {openDB} from 'idb';
async function searchItems (lower, upper) {
if (!lower === '' && upper === '') {
return;
}
let range;
if (lower !== '' && upper !== '') {
range = IDBKeyRange.bound(lower, upper);
} else if (lower === '') {
range = IDBKeyRange.upperBound(upper);
} else {
range = IDBKeyRange.lowerBound(lower);
}
const db = await openDB('test-db4', 1);
const tx = await db.transaction('foods', 'readonly');
const index = tx.store.index('price');
// Open a cursor on the designated object store:
let cursor = await index.openCursor(range);
if (!cursor) {
return;
}
// Iterate on the cursor, row by row:
while (cursor) {
// Show the data in the row at the current cursor position:
console.log(cursor.key, cursor.value);
// Advance the cursor to the next row:
cursor = await cursor.continue();
}
}
// Get items priced between one and four dollars:
searchItems(1.00, 4.00);
Im Beispielcode werden zuerst die Werte für die Limits abgerufen und es wird geprüft, ob die Limits vorhanden sind. Im nächsten Codeblock wird entschieden, welche Methode verwendet werden soll, um den Bereich basierend auf den Werten zu begrenzen. Öffnen Sie in der Datenbankinteraktion den Objektspeicher für die Transaktion wie gewohnt und öffnen Sie dann den Index 'price' im Objektspeicher. Mit dem Index 'price' können Sie nach Artikeln nach Preis suchen.
Der Code öffnet dann einen Cursor für den Index und übergibt den Bereich. Der Cursor gibt ein Promise zurück, das das erste Objekt im Bereich darstellt, oder undefined, wenn im Bereich keine Daten vorhanden sind. Die Methode cursor.continue() gibt einen Cursor zurück, der das nächste Objekt darstellt, und wird in der Schleife fortgesetzt, bis das Ende des Bereichs erreicht ist.
Datenbankversionierung
Wenn Sie die Methode openDB() aufrufen, können Sie die Datenbankversionsnummer im zweiten Parameter angeben. In allen Beispielen in diesem Leitfaden ist die Version auf 1 festgelegt. Eine Datenbank kann jedoch auf eine neue Version aktualisiert werden, wenn Sie sie in irgendeiner Weise ändern müssen. Wenn die angegebene Version höher ist als die Version der vorhandenen Datenbank, wird der Callback upgrade im Ereignisobjekt ausgeführt. So können Sie der Datenbank neue Objektspeicher und Indexe hinzufügen.
Das db-Objekt im upgrade-Callback hat eine spezielle oldVersion-Property, die die Versionsnummer der Datenbank angibt, auf die der Browser Zugriff hat.
Sie können diese Versionsnummer an eine switch-Anweisung übergeben, um Codeblöcke im upgrade-Callback basierend auf der vorhandenen Datenbankversionsnummer auszuführen. Beispiel:
import {openDB} from 'idb';
const db = await openDB('example-database', 2, {
upgrade (db, oldVersion) {
switch (oldVersion) {
case 0:
// Create first object store:
db.createObjectStore('store', { keyPath: 'name' });
case 1:
// Get the original object store, and create an index on it:
const tx = await db.transaction('store', 'readwrite');
tx.store.createIndex('name', 'name');
}
}
});
In diesem Beispiel wird die neueste Version der Datenbank auf 2 festgelegt. Wenn dieser Code zum ersten Mal ausgeführt wird, ist die Datenbank im Browser noch nicht vorhanden. Daher ist oldVersion gleich 0 und die switch-Anweisung beginnt bei case 0. In diesem Beispiel wird der Datenbank ein 'store'-Objektspeicher hinzugefügt.
Wichtiger Hinweis: In switch-Anweisungen steht normalerweise nach jedem case-Block ein break. Dies wird hier jedoch bewusst nicht verwendet. Wenn die vorhandene Datenbank also einige Versionen hinterherhinkt oder nicht vorhanden ist, wird der Code durch die restlichen case-Blöcke fortgesetzt, bis er auf dem neuesten Stand ist. Im Beispiel wird die Ausführung im Browser also bis case 1 fortgesetzt und ein name-Index im Objektspeicher store erstellt.
Wenn Sie einen 'description'-Index im Objektspeicher 'store' erstellen möchten, aktualisieren Sie die Versionsnummer und fügen Sie einen neuen case-Block hinzu:
import {openDB} from 'idb';
const db = await openDB('example-database', 3, {
upgrade (db, oldVersion) {
switch (oldVersion) {
case 0:
// Create first object store:
db.createObjectStore('store', { keyPath: 'name' });
case 1:
// Get the original object store, and create an index on it:
const tx = await db.transaction('store', 'readwrite');
tx.store.createIndex('name', 'name');
case 2:
const tx = await db.transaction('store', 'readwrite');
tx.store.createIndex('description', 'description');
}
}
});
Wenn die im vorherigen Beispiel erstellte Datenbank noch im Browser vorhanden ist, ist oldVersion bei der Ausführung 2. Der Browser überspringt case 0 und case 1 und führt den Code in case 2 aus, wodurch ein description-Index erstellt wird. Danach hat der Browser eine Datenbank der Version 3 mit einem store-Objektspeicher mit den Indexen name und description.
Weitere Informationen
Die folgenden Ressourcen bieten weitere Informationen und Kontext zur Verwendung von IndexedDB.
IndexedDB-Dokumentation
idbGitHub-Repository- IndexedDB verwenden
- Grundlegende Konzepte von IndexedDB
- Indexed Database API 3.0-Spezifikation