JavaScript und IndexedDB

JavaScript und IndexedDB

In diesem Artikel erklären wir JavaScript und IndexedDB.

Dieses Tutorial bietet eine schrittweise Erklärung von JavaScript und IndexedDB mit praktischem Beispielcode bei jedem Schritt, um Ihr Verständnis zu vertiefen.

YouTube Video

javascript-indexed-db.html
  1<!DOCTYPE html>
  2<html lang="en">
  3<head>
  4  <meta charset="UTF-8">
  5  <title>JavaScript &amp; HTML</title>
  6  <style>
  7    * {
  8        box-sizing: border-box;
  9    }
 10
 11    body {
 12        margin: 0;
 13        padding: 1em;
 14        padding-bottom: 10em;
 15        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 16        background-color: #f7f9fc;
 17        color: #333;
 18        line-height: 1.6;
 19    }
 20
 21    .container {
 22        max-width: 800px;
 23        margin: 0 auto;
 24        padding: 1em;
 25        background-color: #ffffff;
 26        border: 1px solid #ccc;
 27        border-radius: 10px;
 28        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 29    }
 30
 31    .container-flex {
 32        display: flex;
 33        flex-wrap: wrap;
 34        gap: 2em;
 35        max-width: 1000px;
 36        margin: 0 auto;
 37        padding: 1em;
 38        background-color: #ffffff;
 39        border: 1px solid #ccc;
 40        border-radius: 10px;
 41        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 42    }
 43
 44    .left-column, .right-column {
 45        flex: 1 1 200px;
 46        min-width: 200px;
 47    }
 48
 49    h1, h2 {
 50        font-size: 1.2rem;
 51        color: #007bff;
 52        margin-top: 0.5em;
 53        margin-bottom: 0.5em;
 54        border-left: 5px solid #007bff;
 55        padding-left: 0.6em;
 56        background-color: #e9f2ff;
 57    }
 58
 59    button {
 60        display: block;
 61        margin: 1em auto;
 62        padding: 0.75em 1.5em;
 63        font-size: 1rem;
 64        background-color: #007bff;
 65        color: white;
 66        border: none;
 67        border-radius: 6px;
 68        cursor: pointer;
 69        transition: background-color 0.3s ease;
 70    }
 71
 72    button:hover {
 73        background-color: #0056b3;
 74    }
 75
 76    #output {
 77        margin-top: 1em;
 78        background-color: #1e1e1e;
 79        color: #0f0;
 80        padding: 1em;
 81        border-radius: 8px;
 82        min-height: 200px;
 83        font-family: Consolas, monospace;
 84        font-size: 0.95rem;
 85        overflow-y: auto;
 86        white-space: pre-wrap;
 87    }
 88
 89    .highlight {
 90        outline: 3px solid #ffc107; /* yellow border */
 91        background-color: #fff8e1;  /* soft yellow background */
 92        transition: background-color 0.3s ease, outline 0.3s ease;
 93    }
 94
 95    .active {
 96        background-color: #28a745; /* green background */
 97        color: #fff;
 98        box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
 99        transition: background-color 0.3s ease, box-shadow 0.3s ease;
100    }
101  </style>
102</head>
103<body>
104    <div class="container">
105        <h1>JavaScript Console</h1>
106        <button id="executeBtn">Execute</button>
107        <div id="output"></div>
108    </div>
109
110    <script>
111        // Override console.log to display messages in the #output element
112        (function () {
113            // Override console.log
114            const originalLog = console.log;
115            console.log = function (...args) {
116                originalLog.apply(console, args);
117                const message = document.createElement('div');
118                message.textContent = args
119                    .map(arg => (typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg)))
120                    .join(" ");
121                output.appendChild(message);
122            };
123
124            // Override console.error
125            const originalError = console.error;
126            console.error = function (...args) {
127                originalError.apply(console, args);
128                const message = document.createElement('div');
129                message.textContent = args
130                    .map(arg => (typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg)))
131                    .join(" ");
132                message.style.color = 'red'; // Color error messages red
133                output.appendChild(message);
134            };
135        })();
136
137        document.getElementById('executeBtn').addEventListener('click', () => {
138            // Prevent multiple loads
139            if (document.getElementById('externalScript')) return;
140
141            const script = document.createElement('script');
142            script.src = 'javascript-indexed-db.js';
143            script.id = 'externalScript';
144            //script.onload = () => console.log('javascript-indexed-db.js loaded and executed.');
145            //script.onerror = () => console.log('Failed to load javascript-indexed-db.js.');
146            document.body.appendChild(script);
147        });
148    </script>
149</body>
150</html>

JavaScript und IndexedDB

IndexedDB ist eine asynchrone Key-Value-Datenbank, die in Browser integriert ist. Sie bietet Funktionen ähnlich wie relationale Datenbanken und ermöglicht es, große Mengen strukturierter Daten auf der Client-Seite zu speichern und zu durchsuchen. Sie ist besonders nützlich für offlinefähige Anwendungen und PWAs (Progressive Web Apps).

Funktionen von IndexedDB

  • Funktioniert asynchron und ereignisgesteuert.
  • JavaScript-Objekte können in Object Stores gespeichert werden.
  • Suche mittels Abfragen oder Indizes ist möglich.
  • Sie hat eine große Speicherkapazität (Hunderte von MB oder mehr), sodass Sie deutlich mehr Daten als mit Cookies oder localStorage speichern können.

Öffnen und Erstellen einer Datenbank

Um IndexedDB zu verwenden, müssen Sie zunächst eine Datenbank öffnen. Existiert sie nicht, wird sie automatisch erstellt.

1const request = indexedDB.open('MyDatabase', 1); // Specify DB name and version
  • Die open-Methode öffnet die Datenbank asynchron und löst die folgenden drei Ereignisse aus.

onsuccess

1// Fired when database is successfully opened
2request.onsuccess = (event) => {
3    const db = event.target.result; // Database instance
4    console.log('Database opened successfully:', db.name);
5};
  • Das onsuccess-Ereignis wird ausgelöst, wenn die Datenbank erfolgreich geöffnet wurde. Nachfolgende Operationen sollten über request.result ausgeführt werden, welches an diesem Punkt verfügbar ist.

onerror

1// Fired when database fails to open
2request.onerror = (event) => {
3    console.error('Failed to open database:', event.target.error);
4};
  • Das onerror-Ereignis wird ausgelöst, wenn das Öffnen der Datenbank fehlschlägt. Hier sollten Fehler protokolliert und behandelt werden.

onupgradeneeded

 1// Fired when database is newly created or upgraded
 2request.onupgradeneeded = (event) => {
 3    const db = event.target.result;
 4    console.log('Database upgrade needed (or newly created):', db.name);
 5
 6    // Example: Create an object store (like a table) if it doesn’t exist
 7    if (!db.objectStoreNames.contains('users')) {
 8        db.createObjectStore('users', { keyPath: 'id' });
 9        console.log('Object store "users" created');
10    }
11};
  • onupgradeneeded wird ausgelöst, wenn die Datenbank neu erstellt wurde oder die angegebene Version höher als die aktuelle Version ist. Das Erstellen von Tabellen (Object Stores) und das Definieren des Schemas sollte in diesem Moment erfolgen.

Erstellen eines Object Stores

Zuerst wird im onupgradeneeded-Ereignis ein 'Object Store' (vergleichbar mit einer Tabelle) erstellt.

1request.onupgradeneeded = function (event) {
2    const db = event.target.result;
3    console.log("onupgradeneeded triggered. Database version:", db.version);
4    if (!db.objectStoreNames.contains('users')) {
5        console.log("Creating object store: users");
6        const store = db.createObjectStore('users', { keyPath: 'id' });
7        store.createIndex('name', 'name', { unique: false });
8    }
9};

Hier werden die folgenden Einstellungen vorgenommen:.

  • createObjectStore createObjectStore ist eine Methode zum Erstellen eines neuen Object Stores in der Datenbank. In einem Object Store können Sie Datensätze speichern und über Optionen wie keyPath verwalten, wie die Daten gespeichert werden. Dieser Vorgang muss innerhalb des onupgradeneeded Ereignisses durchgeführt werden.

  • keyPath: 'id' Setzen Sie keyPath auf die Eigenschaft id, damit jeder Datensatz eindeutig durch einen Primärschlüssel identifiziert wird. So kann id automatisch beim Hinzufügen, Suchen oder Aktualisieren von Daten verwendet werden.

  • createIndex Verwenden Sie die Methode createIndex, um für Suchzwecke einen Index auf der Eigenschaft name zu erstellen. Durch das Setzen von unique: false sind mehrere Datensätze mit demselben name erlaubt.

Umgang mit erfolgreichen Datenbankverbindungen

Weisen Sie die auszuführenden Prozesse bei einer erfolgreichen Datenbankverbindung dem onsuccess-Ereignis zu. In diesem Prozess erhalten Sie die Datenbankinstanz und bereiten das Lesen und Schreiben von Daten vor.

1request.onsuccess = function (event) {
2    const db = event.target.result;
3    console.log('Database opened successfully');
4    // Use db for reading and writing in subsequent operations
5};
  • Dieser Prozess wird ausgeführt, wenn die Verbindung zur IndexedDB-Datenbank erfolgreich hergestellt wurde.

  • event.target.result enthält die geöffnete Datenbankinstanz (IDBDatabase Objekt), mit der Transaktionen gestartet und Object Stores angesprochen werden können.

  • Tatsächliche Lese- und Schreiboperationen wie das Hinzufügen, Abrufen, Aktualisieren und Löschen von Daten werden mit dem db-Objekt ausgeführt.

An diesem Punkt ist die Datenbank bereit, sodass Sie sicher Transaktionen starten können.

Daten hinzufügen

So fügen Sie neue Daten zu IndexedDB hinzu:.

 1function addUser(db, user) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const request = store.add(user);
 5
 6    request.onsuccess = () => {
 7        console.log('User added:', user);
 8    };
 9
10    request.onerror = () => {
11        console.error('Add failed:', request.error);
12    };
13}
14
15// Example: Add a user
16request.onsuccess = function (event) {
17    const db = event.target.result;
18    addUser(db, { id: 1, name: 'Alice' });
19    addUser(db, { id: 2, name: 'Bob' });
20    addUser(db, { id: 3, name: 'John' });
21};
  • Erstellen Sie eine Transaktion mit db.transaction() und geben Sie den Object Store sowie den Modus an (in diesem Fall readwrite).
  • Fügen Sie neue Daten mit der Methode store.add() hinzu.
  • Transaktionen werden automatisch abgeschlossen, aber wenn Sie mehrere Operationen gruppieren möchten, können Sie diese mit Transaktions-Endereignissen steuern.

Daten abrufen (Primärschlüssel-Suche)

So rufen Sie mit dem Primärschlüssel gezielt Daten ab:.

 1function getUserById(db, id) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const request = store.get(id);
 5
 6    request.onsuccess = () => {
 7        if (request.result) {
 8            console.log('User found:', request.result);
 9        } else {
10            console.log('User not found');
11        }
12    };
13
14    request.onerror = () => {
15        console.error('Error retrieving user:', request.error);
16    };
17}
18
19// Example: Get a user by id
20request.onsuccess = function (event) {
21    const db = event.target.result;
22    getUserById(db, 1);
23};
  • Erstellen Sie eine schreibgeschützte Transaktion mit db.transaction().
  • Verwenden Sie store.get(id), um die zum angegebenen Primärschlüssel gehörenden Daten abzurufen.
  • onsuccess wird aufgerufen, wenn der Abruf erfolgreich war. Das Ergebnis wird angezeigt, falls vorhanden. Gibt es kein Ergebnis, gilt dies als 'keine Entsprechung gefunden'.

Suche per Index

Wenn Sie nach anderen Eigenschaften als dem Primärschlüssel suchen möchten, verwenden Sie den zuvor erstellten Index.

 1function getUserByName(db, name) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const index = store.index('name');
 5    const request = index.get(name);
 6
 7    request.onsuccess = () => {
 8        if (request.result) {
 9            console.log('User by name:', request.result);
10        } else {
11            console.log('No user found with that name');
12        }
13    };
14
15    request.onerror = () => {
16        console.error('Error retrieving user by name:', request.error);
17    };
18}
19
20// Example: Get a user by id
21request.onsuccess = function (event) {
22    const db = event.target.result;
23    getUserByName(db, 'Alice');
24};
  • Greifen Sie mit store.index('name') auf den bereits erstellten name-Index zu.
  • index.get(value) gibt den ersten Datensatz mit dem passenden Wert zurück. Wenn mehrere Datensätze denselben Wert haben, können Sie mit index.getAll(value) alle davon abrufen.

Daten aktualisieren

Um vorhandene Daten zu aktualisieren (überschreiben), verwenden Sie die Methode put().

Existiert ein Datensatz mit passendem Primärschlüssel, wird er aktualisiert. Andernfalls wird ein neuer Datensatz angelegt.

 1function updateUser(db, updatedUser) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const request = store.put(updatedUser);
 5
 6    request.onsuccess = () => {
 7        console.log('User updated:', updatedUser);
 8    };
 9
10    request.onerror = () => {
11        console.error('Update failed:', request.error);
12    };
13}
14
15// Example: Update user
16request.onsuccess = async (event) => {
17    const db = event.target.result;
18
19    // Test : update existing user
20    updateUser(db, { id: 3, name: 'John Updated' });
21
22    // Test : insert new user
23    updateUser(db, { id: 4, name: 'Charlie' });
24};
  • put() ist eine praktische Methode, die sowohl das Aktualisieren als auch das Hinzufügen unterstützt.
  • Wenn bereits Daten mit demselben Primärschlüssel existieren, werden diese überschrieben.
  • Wenn Sie vor dem Aktualisieren das Vorhandensein prüfen möchten, können Sie dies mit get() vorher bestätigen.

Daten löschen

Um Daten zum angegebenen Primärschlüssel zu löschen, verwenden Sie die Methode delete().

 1function deleteUser(db, id) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const request = store.delete(id);
 5
 6    request.onsuccess = () => {
 7        console.log(`User with id=${id} deleted successfully`);
 8    };
 9
10    request.onerror = () => {
11        console.error(`Failed to delete user with id=${id}:`, request.error);
12    };
13}
14
15// Example: Delete a user by id
16request.onsuccess = function (event) {
17    const db = event.target.result;
18    deleteUser(db, 4);
19};
  • Verwenden Sie store.delete(id), um Daten mit übereinstimmendem Primärschlüssel zu löschen.
  • Auch wenn die Daten nicht existieren, tritt kein Fehler auf und der Vorgang gilt als erfolgreich.
  • Durch die Implementierung einer Fehlerbehandlung wird Ihr Code robuster.

Alle Daten abrufen

getAll()

Um alle Datensätze eines Object Stores abzurufen, verwenden Sie die Methode getAll().

 1function getAllUsers(db) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const request = store.getAll();
 5
 6    request.onsuccess = () => {
 7        console.log('All users:', request.result);
 8    };
 9
10    request.onerror = () => {
11        console.error('Failed to retrieve users:', request.error);
12    };
13}
14
15// Example: Get all users
16request.onsuccess = function (event) {
17    const db = event.target.result;
18    getAllUsers(db);
19};
  • getAll() ruft alle Datensätze des angegebenen Object Stores als Array ab.
  • Auch beim Abrufen großer Datenmengen auf einmal erfolgt die Verarbeitung effizient.
  • Die Ergebnisse werden als Array in request.result gespeichert.
  • Fügen Sie immer eine Fehlerbehandlung hinzu, um Fehler abfangen zu können.

openCursor()

openCursor() ist eine Methode für das sequenzielle Durchlaufen von Datensätzen in einem Object Store oder Index. Sie ist nützlich, wenn Sie Daten einzeln verarbeiten möchten, anstatt große Mengen auf einmal abzurufen.

 1function getAllUsersWithCursor(db) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const request = store.openCursor();
 5
 6    request.onsuccess = () => {
 7        const cursor = request.result;
 8        if (cursor) {
 9            console.log('User:', cursor.value); // Process the current record
10            cursor.continue(); // Move to the next record
11        } else {
12            console.log('All users have been processed.');
13        }
14    };
15
16    request.onerror = () => {
17        console.error('Failed to open cursor:', request.error);
18    };
19}
20
21// Example: Get all users
22request.onsuccess = function (event) {
23    const db = event.target.result;
24    getAllUsersWithCursor(db);
25};
  • Mit openCursor() starten Sie einen Cursor und rufen alle Datensätze im Object Store einzeln ab.
  • Mit cursor.value erhalten Sie das Datenobjekt des aktuellen Datensatzes.
  • Bewegen Sie den Cursor mit cursor.continue() zum nächsten Datensatz.
  • Wenn cursor === null ist, wurden alle Datensätze durchlaufen.

Beispiel für einen Aktualisierungsvorgang mit openCursor()

Zum Beispiel sieht der Vorgang, um den Namen eines Benutzers mit name Alice zu Alicia zu ändern, so aus:.

 1function updateUserName(db, oldName, newName) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const index = store.index('name'); // Use the 'name' index
 5    const request = index.openCursor(IDBKeyRange.only(oldName));
 6
 7    request.onsuccess = () => {
 8        const cursor = request.result;
 9        if (cursor) {
10            const user = cursor.value;
11            user.name = newName; // Update the name
12            const updateRequest = cursor.update(user);
13
14            updateRequest.onsuccess = () => {
15                console.log('Updated user:', user);
16            };
17
18            updateRequest.onerror = () => {
19                console.error('Failed to update user:', updateRequest.error);
20            };
21
22            cursor.continue();
23        } else {
24            console.log('All matching users have been updated.');
25        }
26    };
27
28    request.onerror = () => {
29        console.error('Cursor error:', request.error);
30    };
31}
32
33// Example: Update user name
34request.onsuccess = function (event) {
35    const db = event.target.result;
36    updateUserName(db, 'Alice', 'Alicia');
37};
  • IDBKeyRange.only(oldName) Mit IDBKeyRange.only können Sie nur die Datensätze auswählen, deren Schlüssel genau mit oldName übereinstimmt. Dies ist nützlich, wenn Sie direkt auf einen bestimmten Wert zugreifen möchten.

  • cursor.update() Nachdem cursor.value aktualisiert wurde, überschreibt ein Aufruf von update() den entsprechenden Datensatz.

  • Verarbeitung mehrerer Übereinstimmungen Mit cursor.continue() können Sie den Cursor zum nächsten passenden Datensatz bewegen. So können Sie mehrere Datensätze mit demselben Schlüssel oder derselben Bedingung nacheinander verarbeiten.

  • Fehlerbehandlung Durch das Ausgeben von Logs im onerror-Fall wird es einfacher, Ursachen zu untersuchen und während des Betriebs Fehler zu beheben.

Beispiel für einen Löschvorgang mit openCursor()

Zum Beispiel sieht der Vorgang, alle Benutzer mit dem name Bob zu löschen, so aus:.

 1function deleteUsersByName(db, targetName) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const index = store.index('name');
 5    const request = index.openCursor(IDBKeyRange.only(targetName));
 6
 7    request.onsuccess = () => {
 8        const cursor = request.result;
 9        if (cursor) {
10            const deleteRequest = cursor.delete();
11
12            deleteRequest.onsuccess = () => {
13                console.log('Deleted user:', cursor.value);
14            };
15
16            deleteRequest.onerror = () => {
17                console.error('Failed to delete user:', deleteRequest.error);
18            };
19
20            cursor.continue();
21        } else {
22            console.log('All matching users have been deleted.');
23        }
24    };
25
26    request.onerror = () => {
27        console.error('Cursor error:', request.error);
28    };
29}
30
31// Example: Delete user by name
32request.onsuccess = function (event) {
33    const db = event.target.result;
34    deleteUsersByName(db, 'Bob');
35};
  • cursor.delete() Mit cursor.delete() wird der Datensatz an der aktuellen Cursor-Position gelöscht. Da das Ergebnis asynchron zurückgegeben wird, können Sie den Vorgang in onsuccess überprüfen.

Hinweise zu Transaktionen und asynchroner Verarbeitung

IndexedDB ist asynchron und ereignisgesteuert. Alle Vorgänge müssen mit onsuccess- oder onerror-Ereignissen behandelt werden. Wenn Sie mehrere Vorgänge bündeln möchten, ist es praktisch, diese in ein Promise zu kapseln.

 1function openDatabase() {
 2    return new Promise((resolve, reject) => {
 3        const request = indexedDB.open('MyDatabase', 1);
 4
 5        request.onupgradeneeded = function (event) {
 6            const db = event.target.result;
 7
 8            // Initialization process (creating object stores and indexes, etc.)
 9            const store = db.createObjectStore('users', { keyPath: 'id' });
10            store.createIndex('name', 'name', { unique: false });
11        };
12
13        request.onsuccess = function (event) {
14            const db = event.target.result;
15            resolve(db);
16        };
17
18        request.onerror = function () {
19            reject(request.error);
20        };
21    });
22}
23
24function addUserAsync(db, user) {
25    return new Promise((resolve, reject) => {
26        const transaction = db.transaction('users', 'readwrite');
27        const store = transaction.objectStore('users');
28        const request = store.add(user);
29
30        request.onsuccess = () => resolve();
31        request.onerror = () => reject(request.error);
32    });
33}
34
35async function main() {
36    try {
37        const db = await openDatabase();
38        await addUserAsync(db, { id: 1, name: 'Alice' });
39        console.log('User added successfully');
40    } catch (error) {
41        console.error('Error:', error);
42    }
43}
  • Wenn Sie Funktionen wie openDatabase oder addUserAsync mit einem Promise umhüllen, können Sie asynchrone Vorgänge intuitiv mit async/await behandeln.
  • Dadurch wird das sogenannte Callback-Hell vermieden und der Code wird lesbarer.

Zusammenfassung

IndexedDB ist eine sehr leistungsfähige Funktion, wenn Sie fortgeschrittenes Datenmanagement auf der Browser-Seite durchführen möchten. Anfangs kann die ereignisbasierte asynchrone Verarbeitung verwirrend sein, aber sobald Sie die Struktur verstehen, können Sie umfassende Datenoperationen auf der Clientseite durchführen.

Insbesondere das Beachten der folgenden Punkte hilft, die Nutzung zu erleichtern:.

  • Führen Sie die Ersteinrichtung mit onupgradeneeded durch.
  • Achten Sie auf den Lese-/Schreibmodus von Transaktionen.
  • Indizes ermöglichen effiziente Suchvorgänge.
  • Daten können als Objekte gespeichert werden, was eine hohe Kompatibilität mit JSON gewährleistet.

Wenn Sie IndexedDB beherrschen, wird das Datenmanagement für PWAs und Offline-Apps erheblich erleichtert.

Sie können den obigen Artikel mit Visual Studio Code auf unserem YouTube-Kanal verfolgen. Bitte schauen Sie sich auch den YouTube-Kanal an.

YouTube Video