Best Practices für die Verwendung von IndexedDB

Hier finden Sie Best Practices zum Synchronisieren des Anwendungsstatus zwischen IndexedDB und gängigen Statusverwaltungsbibliotheken.

Wenn ein Nutzer zum ersten Mal eine Website oder App lädt, ist oft sehr viel Arbeit damit verbunden, den anfänglichen Anwendungsstatus zu erstellen, der zum Rendern der UI verwendet wird. Beispielsweise kann die Anwendung manchmal den Nutzer clientseitig authentifizieren und dann mehrere API-Anfragen stellen, bevor alle für die Anzeige auf der Seite erforderlichen Daten zur Verfügung stehen.

Das Speichern des Anwendungsstatus in IndexedDB kann eine gute Möglichkeit sein, die Ladezeit für wiederholte Besuche zu verkürzen. Die App kann sich dann im Hintergrund mit beliebigen API-Diensten synchronisieren und die Benutzeroberfläche verzögert mit neuen Daten aktualisieren. Dabei wird die Strategie stale-while-revalidator verwendet.

Eine weitere sinnvolle Verwendung von IndexedDB ist das Speichern von nutzergenerierten Inhalten, entweder als temporärer Speicher, bevor sie auf den Server hochgeladen werden, oder als clientseitiger Cache von Remote-Daten – oder natürlich beides.

Bei der Verwendung von IndexedDB müssen jedoch viele wichtige Aspekte berücksichtigt werden, die für Entwickler, die mit den APIs noch nicht vertraut sind, möglicherweise nicht sofort offensichtlich sind. In diesem Artikel werden häufig gestellte Fragen beantwortet und einige der wichtigsten Punkte erläutert, die Sie beachten sollten, wenn Sie Daten in IndexedDB speichern.

App vorhersehbar halten

Viele der Komplexitäten von IndexedDB ergeben sich aus der Tatsache, dass es so viele Faktoren gibt, über die Sie (der 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 auf allen Plattformen in IndexedDB gespeichert werden

Wenn Sie große, vom Nutzer erstellte Dateien wie Bilder oder Videos speichern, können Sie versuchen, diese als File- oder Blob-Objekte zu speichern. Dies funktioniert auf einigen Plattformen, aber auf anderen schlägt er fehl. Insbesondere Safari unter iOS kann Blobs nicht in IndexedDB speichern.

Glücklicherweise ist es nicht allzu schwierig, eine Blob in eine 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, um die Konvertierung korrekt durchzuführen.

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

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 der Lesevorgang abgeschlossen ist, wird ein loadend-Ereignis auf dem Lesegerät ausgelöst. Sie können diesen Prozess so in einen Promise umschließen:

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);
  });
}

Schreiben in den Speicher schlagen möglicherweise fehl

Fehler beim Schreiben in IndexedDB können aus verschiedenen Gründen auftreten. In einigen Fällen haben Entwickler keinen Einfluss darauf. Beispielsweise erlauben einige Browser derzeit das Schreiben in IndexedDB im Modus für privates Surfen nicht. Es besteht auch die Möglichkeit, dass ein Nutzer ein Gerät verwendet, auf dem fast nicht mehr genügend Speicherplatz vorhanden ist, sodass der Browser Sie daran hindert, Inhalte zu speichern.

Aus diesem Grund ist es von entscheidender Bedeutung, dass Sie in Ihrem IndexedDB-Code immer eine ordnungsgemäße Fehlerbehandlung implementieren. Daher empfiehlt es sich im Allgemeinen, den App-Status nicht nur zu speichern, sondern auch im Arbeitsspeicher zu speichern, damit die Benutzeroberfläche beim Ausführen im Modus für privates Surfen oder wenn kein Speicherplatz verfügbar ist (auch wenn einige andere App-Funktionen, die Speicher erfordern, nicht funktionieren).

Sie können Fehler in IndexedDB-Vorgängen erkennen, 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 den unbefugten Zugriff einschränken können, sind clientseitige Datenbanken für Browsererweiterungen und Entwicklertools zugänglich und können vom Nutzer gelöscht werden.

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

Gespeicherte Daten sind möglicherweise veraltet

Ähnlich wie im vorherigen Abschnitt kann es auch sein, dass die im Speicher gespeicherten Daten von einer alten Version Ihres Codes geschrieben wurden, auch wenn der Nutzer die Daten nicht selbst geändert hat. Diese Version kann Fehler enthalten.

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

Unittests 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 Hauptmerkmale von IndexedDB ist die asynchrone API. Sie sollten sich aber nicht täuschen, dass Sie sich bei der Verwendung keine Gedanken über die Leistung machen müssen. Es gibt eine Reihe von Fällen, in denen eine unsachgemäße Nutzung dennoch den Hauptthread blockieren kann, was zu Verzögerungen und einer nicht reagierenden Reaktion führen kann.

Im Allgemeinen sollten die Lese- und Schreibvorgänge in IndexedDB nicht größer sein als für die Daten erforderlich, auf die zugegriffen wird.

Obwohl IndexedDB das Speichern großer, verschachtelter Objekte als einzelnen Datensatz ermöglicht (und dies aus Entwicklersicht zugegebenermaßen recht praktisch ist), sollte diese Vorgehensweise vermieden werden. Dies liegt daran, dass beim Speichern eines Objekts von IndexedDB zuerst ein strukturierter Klon dieses Objekts erstellt werden muss und der strukturierte Klonvorgang im Hauptthread stattfindet. Je größer das Objekt, desto länger die Sperrzeit.

Dies ist bei der Planung der Beibehaltung des Anwendungsstatus in IndexedDB mit einigen Herausforderungen verbunden, da die meisten gängigen Zustandsverwaltungsbibliotheken (wie Redux) Ihre gesamte Statusstruktur als einzelnes JavaScript-Objekt verwalten.

Auch wenn die Statusverwaltung auf diese Weise viele Vorteile hat (z. B., dass Ihr Code leicht zu verstehen ist und Fehler behoben werden können), mag es verlockend und praktisch sein, den gesamten Statusbaum als einzelnen Datensatz in IndexedDB zu speichern. Wenn Sie dies nach jeder Änderung tun (selbst bei gedrosseltem/entsprungenem Code), wird der Hauptthread nicht unnötig blockiert und in einigen Fällen wird der Tab sogar nicht mehr reagieren.

Anstatt den gesamten Statusbaum 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 die Kosten für den Abruf der Binärdatei zu bezahlen.

Wie bei den meisten Best Practices ist dies keine Alles-oder-Nichts-Regel. In Fällen, in denen es nicht möglich ist, ein Statusobjekt aufzuteilen und nur den minimalen Änderungssatz zu schreiben, ist es besser, die Daten in Unterstrukturen aufzuteilen und nur diese zu schreiben, als immer den gesamten Statusbaum zu schreiben. Kleine Verbesserungen sind besser als gar keine.

Und schließlich sollten Sie immer die Auswirkungen auf die Leistung des von Ihnen geschriebenen Codes messen. Kleine Schreibvorgänge in IndexedDB führen in der Tat zu einer besseren Leistung als große Schreibvorgänge. Dies ist jedoch nur wichtig, 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, worauf Sie optimieren.

Ergebnisse

Entwickler können Client-Speichermechanismen wie IndexedDB nutzen, um die Nutzerfreundlichkeit ihrer Anwendung zu verbessern, indem sie den Status nicht nur sitzungsübergreifend beibehalten, sondern auch die Zeit verkürzen, die zum Laden des Anfangszustands bei wiederholten Besuchen benötigt wird.

Die ordnungsgemäße Verwendung von IndexedDB kann die Nutzerfreundlichkeit erheblich verbessern. Wird sie falsch eingesetzt oder Fehlerfälle nicht behoben, kann dies zu fehlerhaften Anwendungen und unzufriedenen Nutzern führen.

Da der Clientspeicher viele Faktoren beinhaltet, die außerhalb Ihrer Kontrolle liegen, ist es wichtig, dass Ihr Code gut getestet wird und ordnungsgemäß Fehler behandelt, selbst solche, die anfangs unwahrscheinlich sind.