Best Practices für die Verwendung von IndexedDB

Lernen Sie Best Practices für die Synchronisierung des Anwendungsstatus zwischen IndexedDB und gängigen Bibliotheken zur Statusverwaltung kennen.

Wenn ein Nutzer zum ersten Mal eine Website oder App lädt, ist oft ein großer Arbeitsaufwand in der Erstellung des anfänglichen App-Status erforderlich, der zum Rendern der UI verwendet wird. Manchmal muss die Anwendung beispielsweise den Nutzer clientseitig authentifizieren und dann mehrere API-Anfragen senden, bevor alle erforderlichen Daten zur Anzeige auf der Seite vorhanden sind.

Durch das Speichern des Anwendungsstatus in IndexedDB lässt sich die Ladezeit bei wiederholten Besuchen verkürzen. Die Anwendung kann dann im Hintergrund mit allen API-Diensten synchronisiert und die UI nach und nach mit neuen Daten aktualisiert werden. Dabei wird die Strategie stale- while-revalidation eingesetzt.

Eine weitere gute Verwendung von IndexedDB ist das Speichern von nutzergenerierten Inhalten, entweder als temporärer Speicher vor dem Hochladen auf den Server oder als clientseitiger Cache mit Remotedaten – oder natürlich beides.

Bei der Verwendung von IndexedDB sind jedoch viele wichtige Aspekte zu beachten, die für Entwickler, die die APIs neu verwenden, möglicherweise nicht sofort offensichtlich sind. In diesem Artikel werden häufige Fragen beantwortet und einige der wichtigsten Aspekte erläutert, die Sie beim Speichern von Daten in IndexedDB beachten sollten.

Die App vorhersehbar gestalten

IndexedDB ist sehr komplex, weil es so viele Faktoren gibt, über die Sie als Entwickler keine Kontrolle haben. In diesem Abschnitt werden viele der Probleme behandelt, die Sie bei der Arbeit mit IndexedDB berücksichtigen müssen.

Nicht alles kann in IndexedDB auf allen Plattformen gespeichert werden

Wenn Sie große, von Nutzern erstellte Dateien wie Bilder oder Videos speichern, können Sie versuchen, sie als File- oder Blob-Objekte zu speichern. Dies funktioniert auf einigen Plattformen, auf anderen jedoch nicht. Insbesondere Safari unter iOS kann Blobs nicht in IndexedDB speichern.

Zum Glück ist es nicht allzu schwierig, einen Blob in einen ArrayBuffer umzuwandeln und umgekehrt. Das Speichern von ArrayBuffers in IndexedDB wird sehr gut unterstützt.

Denken Sie jedoch daran, dass Blob einen MIME-Typ hat, ArrayBuffer jedoch nicht. Sie müssen den Typ zusammen mit dem Zwischenspeicher speichern, damit die Konvertierung ordnungsgemäß erfolgt.

Zum Konvertieren eines ArrayBuffer-Objekts in ein Blob-Objekt verwenden Sie einfach den Konstruktor Blob.

function arrayBufferToBlob(buffer, type) {
  return new Blob([buffer], { type: type });
}

Die andere Richtung ist etwas komplizierter und ein asynchroner Prozess. Sie können ein FileReader-Objekt verwenden, um das Blob als ArrayBuffer zu lesen. Wenn das Lesen abgeschlossen ist, wird ein loadend-Ereignis auf dem Lesegerät ausgelöst. Sie können diesen Prozess so in einem Promise zusammenfassen:

function blobToArrayBuffer(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.addEventListener('loadend', () => {
      resolve(reader.result);
    });
    reader.addEventListener('error', reject);
    reader.readAsArrayBuffer(blob);
  });
}

Fehler beim Schreiben in den Speicher

Fehler beim Schreiben in IndexedDB können aus verschiedenen Gründen auftreten. In einigen Fällen haben Sie als Entwickler keine Kontrolle. Beispielsweise ist bei einigen Browsern derzeit das Schreiben in IndexedDB im Modus für privates Surfen nicht zulässig. Es kann auch sein, dass ein Nutzer ein Gerät verwendet, auf dem fast nicht mehr genügend Speicherplatz verfügbar ist, und der Browser verhindert, dass Sie überhaupt etwas speichern.

Aus diesem Grund ist es von entscheidender Bedeutung, dass Sie im IndexedDB-Code immer eine ordnungsgemäße Fehlerbehandlung implementieren. Das bedeutet auch, dass es generell empfehlenswert ist, den Anwendungsstatus im Arbeitsspeicher beizubehalten (zusätzlich zur Speicherung), damit die UI auch dann nicht abbricht, wenn im Modus für privates Surfen ausgeführt wird oder kein Speicherplatz verfügbar ist. Dies gilt auch dann, wenn einige der anderen Anwendungsfunktionen, für die Speicher erforderlich ist, nicht funktionieren.

Sie können Fehler in IndexedDB-Vorgängen abfangen, indem Sie dem error-Ereignis einen Event-Handler hinzufügen, wenn Sie ein IDBDatabase-, IDBTransaction- oder IDBRequest-Objekt erstellen.

const request = db.open('example-db', 1);
request.addEventListener('error', (event) => {
  console.log('Request error:', request.error);
};

Gespeicherte Daten wurden möglicherweise vom Nutzer geändert oder gelöscht

Im Gegensatz zu serverseitigen Datenbanken, bei denen Sie nicht autorisierten Zugriff einschränken können, sind clientseitige Datenbanken für Browsererweiterungen und Entwicklertools zugänglich und können vom Nutzer gelöscht werden.

Es ist zwar ungewöhnlich, dass Nutzer ihre lokal gespeicherten Daten ändern, es ist jedoch üblich, dass Nutzer diese löschen. Es ist wichtig, dass Ihre Anwendung beide Fälle fehlerfrei verarbeiten kann.

Gespeicherte Daten sind möglicherweise veraltet

Ähnlich wie im vorherigen Abschnitt ist es auch möglich, dass die Daten, die sich im Speicher befinden, von einer alten Version Ihres Codes geschrieben wurden, selbst wenn der Nutzer die Daten selbst nicht geändert hat, möglicherweise eine Version mit Fehlern.

IndexedDB unterstützt Schemaversionen und Upgrades über die Methode IDBOpenDBRequest.onupgradeneeded(). Sie müssen Ihren Upgradecode jedoch trotzdem so schreiben, dass er Nutzer aus einer früheren Version (einschließlich einer Version mit einem Fehler) verarbeiten kann.

Einheitentests können hier sehr hilfreich sein, da es oft nicht möglich ist, alle möglichen Upgradepfade und -fälle manuell zu testen.

Leistung der App aufrechterhalten

Eines der wichtigsten Merkmale von IndexedDB ist die asynchrone API. Sie müssen sich jedoch keine Gedanken über die Leistung machen, wenn Sie sie verwenden. Es gibt eine Reihe von Fällen, in denen eine nicht ordnungsgemäße Nutzung den Hauptthread dennoch blockieren kann, was zu Verzögerungen und einer Nichtreaktion führen kann.

Generell sollte der Wert von Lese- und Schreibvorgängen in IndexedDB für die Daten, auf die zugegriffen wird, nicht überschritten werden.

IndexedDB ermöglicht zwar das Speichern großer, verschachtelter Objekte in einem einzigen Datensatz, was aus Entwicklerperspektive zugegebenermaßen recht praktisch ist, sollte jedoch vermieden werden. Das liegt daran, dass IndexedDB beim Speichern eines Objekts zuerst einen strukturierten Klon dieses Objekts erstellen muss und der strukturierte Klonvorgang im Hauptthread erfolgt. Je größer das Objekt, desto länger ist die Blockierzeit.

Dies bringt einige Herausforderungen bei der Planung der Beibehaltung des Anwendungsstatus in IndexedDB mit sich, da die meisten gängigen State Management-Bibliotheken (wie Redux) Ihre gesamte Zustandsstruktur als einzelnes JavaScript-Objekt verwalten.

Das Verwalten des Status auf diese Weise hat viele Vorteile (z. B. erleichtert es, den Code zu analysieren und Fehler zu beheben). Auch wenn es verlockend und praktisch sein kann, den gesamten Zustandsbaum als einzelnen Eintrag in IndexedDB zu speichern, kann es verlockend und praktisch sein, dies nach jeder Änderung zu tun (selbst wenn der Hauptthread gedrosselt/entprallt wird), was dazu führt, dass der Hauptthread unnötig blockiert wird, die Wahrscheinlichkeit von Schreibfehlern aber steigt oder in einigen Fällen zu einem Absturz führt.

Anstatt den gesamten Zustandsbaum in einem einzigen Datensatz zu speichern, sollten Sie ihn in einzelne Datensätze aufteilen und nur die Datensätze aktualisieren, die sich tatsächlich ändern.

Dasselbe gilt, wenn Sie große Elemente wie Bilder, Musik oder Videos in IndexedDB speichern. Speichern Sie jedes Element mit einem eigenen Schlüssel und nicht in einem größeren Objekt. So können Sie die strukturierten Daten abrufen, ohne zusätzlich die Kosten für das Abrufen der Binärdatei zu bezahlen.

Wie bei den meisten Best Practices gilt auch hier keine Alles-oder-Nichts-Regel. In Fällen, in denen es nicht möglich ist, ein Zustandsobjekt aufzuteilen und nur den minimalen Änderungssatz zu schreiben, sollten Sie die Daten in Unterstrukturen aufteilen und nur diese schreiben, um immer den gesamten Zustandsbaum zu schreiben. Kleine Verbesserungen sind besser als gar keine Verbesserungen.

Schließlich sollten Sie immer die Auswirkungen des von Ihnen geschriebenen Codes auf die Leistung messen. Es stimmt zwar, dass kleine Schreibvorgänge in IndexedDB eine bessere Leistung erzielen als große Schreibvorgänge, aber dies ist nur relevant, wenn die Schreibvorgänge in IndexedDB, die Ihre Anwendung ausführt, tatsächlich zu langen Aufgaben führen, die den Hauptthread blockieren und die Nutzerfreundlichkeit beeinträchtigen. Es ist wichtig, zu messen, damit Sie verstehen, wofür Sie optimieren.

Ergebnisse

Entwickler können Clientspeichermechanismen wie IndexedDB nutzen, um die Nutzerfreundlichkeit ihrer Anwendung zu verbessern. Sie können damit nicht nur den Status über mehrere Sitzungen hinweg beibehalten, sondern auch bei wiederholten Besuchen den Startstatus verkürzen.

Die ordnungsgemäße Verwendung von IndexedDB kann die Nutzerfreundlichkeit erheblich verbessern. Wenn Sie IndexedDB jedoch falsch verwenden oder Fehler nicht verarbeiten, kann dies zu fehlerhaften Apps und unzufriedenen Nutzern führen.

Da der Clientspeicher viele Faktoren umfasst, auf die Sie keinen Einfluss haben, ist es wichtig, dass Ihr Code gut getestet ist und Fehler korrekt verarbeitet, auch solche, die anfangs unwahrscheinlich erscheinen.