Sprawdzone metody korzystania z IndexedDB

Poznaj sprawdzone metody synchronizowania stanu aplikacji między IndexedDB popularnymi bibliotekami zarządzania stanami.

Gdy użytkownik po raz pierwszy wczytuje witrynę lub aplikację, utworzenie początkowego stanu aplikacji, która posłuży do renderowania interfejsu, często wymaga sporo pracy. Czasami aplikacja musi np. uwierzytelnić się po stronie klienta, a potem wysłać kilka żądań do interfejsu API, zanim zbierze wszystkie dane, które musi wyświetlić na stronie.

Przechowywanie stanu aplikacji w IndexedDB może być świetnym sposobem na przyspieszenie wczytywania danych w przypadku powtarzających się wizyt. Aplikacja może wtedy zsynchronizować się z dowolnymi usługami interfejsu API w tle i leniwie aktualizować interfejs użytkownika o nowe dane, stosując strategię nieaktywnej w trakcie ponownej weryfikacji.

Innym dobrym zastosowaniem IndexedDB jest przechowywanie treści użytkowników – jako tymczasowy magazyn przed przesłaniem na serwer lub jako pamięć podręczną po stronie klienta dla zdalnych danych – albo oczywiście w obu tych miejscach.

Podczas korzystania z IndexedDB należy jednak pamiętać o wielu ważnych kwestiach, które mogą nie być oczywiste dla programistów, którzy dopiero zaczynają korzystać z interfejsów API. W tym artykule znajdziesz odpowiedzi na najczęstsze pytania i omawiamy najważniejsze kwestie, o których należy pamiętać podczas utrwalania danych w IndexedDB.

Zapewnienie przewidywalności aplikacji

Wiele zawiłości związanych z IndexedDB wynika z faktu, że deweloper nie ma nad nimi kontroli. W tej sekcji omawiamy wiele problemów, o których należy pamiętać podczas pracy z bazą IndexedDB.

Nie wszystko można przechowywać w IndexedDB na wszystkich platformach

Jeśli przechowujesz duże, generowane przez użytkowników pliki (np. obrazy lub filmy), możesz spróbować je zapisać jako obiekty File lub Blob. Na niektórych platformach ta funkcja działa, ale na innych nie działa. Safari w iOS nie może zapisywać elementów Blob w IndexedDB.

Na szczęście przekonwertowanie pola Blob na ArrayBuffer i odwrotnie nie jest zbyt trudne. Przechowywanie obiektów ArrayBuffer w IndexedDB jest bardzo dobrze obsługiwane.

Pamiętaj jednak, że typ MIME Blob jest ograniczony, a ArrayBuffer – nie. Aby przeprowadzić prawidłową konwersję, musisz zapisać typ w buforze.

Aby przekonwertować element ArrayBuffer na Blob, wystarczy użyć konstruktora Blob.

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

Drugi kierunek jest nieco bardziej wymagający i jest procesem asynchronicznym. Aby odczytać obiekt blob jako ArrayBuffer, możesz użyć obiektu FileReader. Po zakończeniu odczytu na czytniku wywoływane jest zdarzenie loadend. Możesz zakończyć ten proces w Promise w następujący sposób:

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

Zapisywanie w pamięci może się nie udać

Błędy podczas zapisywania w IndexedDB mogą wystąpić z wielu powodów, a w niektórych przypadkach są poza Twoją kontrolą jako dewelopera. Na przykład niektóre przeglądarki nie zezwalają obecnie na zapisywanie w IndexedDB w trybie przeglądania prywatnego. Istnieje też możliwość, że użytkownik korzysta z urządzenia, na którym kończy się miejsce na dysku, a przeglądarka całkowicie uniemożliwi Ci zapisywanie jakichkolwiek danych.

Z tego powodu jest niezwykle ważne, aby zawsze wdrażać prawidłową obsługę błędów w kodzie IndexedDB. Oznacza to również, że zwykle dobrym pomysłem jest przechowywanie stanu aplikacji w pamięci (oprócz jej przechowywania), aby interfejs nie ulegał awarii, gdy działa w trybie przeglądania prywatnego lub gdy nie ma dostępnego miejsca (nawet jeśli niektóre inne funkcje aplikacji wymagające miejsca na dane nie będą działać).

Możesz wykrywać błędy w operacjach IndexedDB, dodając moduł obsługi zdarzeń error podczas tworzenia obiektu IDBDatabase, IDBTransaction lub IDBRequest.

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

Przechowywane dane mogły zostać zmodyfikowane lub usunięte przez użytkownika

W przeciwieństwie do baz danych po stronie serwera, w których można ograniczyć nieautoryzowany dostęp, bazy danych po stronie klienta są dostępne dla rozszerzeń przeglądarki i narzędzi dla programistów, a użytkownik może je wyczyścić.

Chociaż użytkownicy rzadko modyfikują swoje dane przechowywane lokalnie, użytkownicy często je usuwają. Ważne jest, aby aplikacja bez problemu poradziła sobie z obu tych przypadkach.

Zapisane dane mogą być nieaktualne

Podobnie jak w przypadku poprzedniej sekcji, nawet jeśli użytkownik nie zmodyfikował swoich danych, może się zdarzyć, że dane, które przechowuje w pamięci, zostały zapisane przez starą wersję kodu, która prawdopodobnie zawiera błędy.

IndexedDB ma wbudowaną obsługę wersji schematu i uaktualnianych za pomocą metody IDBOpenDBRequest.onupgradeneeded(), jednak nadal musisz napisać kod uaktualnienia w taki sposób, by obsługiwał użytkownika przechodzącego z poprzedniej wersji (łącznie z wersją z błędem).

Bardzo pomocne mogą być tu testy jednostkowe, ponieważ często nie ma możliwości ręcznego testowania wszystkich możliwych ścieżek i przypadków uaktualnienia.

Dbanie o wydajność aplikacji

Jedną z kluczowych cech IndexedDB jest asynchroniczny interfejs API, ale nie obawiaj się, że podczas korzystania z niej nie musisz martwić się o wydajność. W wielu przypadkach niewłaściwe użycie może nadal zablokować wątek główny, co może prowadzić do zacinania się i braku reakcji.

Ogólnie wartość liczby odczytów i zapisów w IndexedDB nie powinna przekraczać wartości wymaganej w przypadku danych, do których chcesz uzyskać dostęp.

Chociaż IndexedDB umożliwia przechowywanie dużych, zagnieżdżonych obiektów w jednym rekordzie (co jest przyznawane z punktu widzenia programistów), należy unikać tej metody. Wynika to z tego, że gdy IndexedDB zapisuje obiekt, musi najpierw utworzyć ustrukturyzowany klon tego obiektu, a ustrukturyzowany proces klonowania odbywa się w wątku głównym. Im większy obiekt, tym dłuższy czas blokowania.

Wiąże się to z pewnymi wyzwaniami przy planowaniu sposobu utrwalania stanu aplikacji w IndexedDB, ponieważ większość popularnych bibliotek zarządzania stanami (takich jak Redux) zarządza całym drzewem stanu jako jednym obiektem JavaScriptu.

Ten sposób zarządzania stanem ma wiele zalet (np. ułatwia analizowanie i debugowanie kodu), a chociaż przechowywanie całego drzewa stanu w jednym rekordzie w IndexedDB może być kuszące i wygodne, wykonywanie tej czynności po każdej zmianie (nawet w przypadku ograniczenia/odbicia danych) spowoduje niepotrzebne zablokowanie wątku głównego, a w niektórych przypadkach zwiększy także ryzyko wystąpienia błędów karty, a w niektórych przypadkach spowoduje, że karta nie zareaguje.

Zamiast przechowywać całe drzewo stanu w pojedynczym rekordzie, należy je podzielić na osobne rekordy i zaktualizować tylko te rekordy, które faktycznie się zmieniają.

To samo dotyczy zasobów IndexedDB, jeśli przechowujesz w indeksie IndexedDB duże elementy, takie jak obrazy, muzyka czy filmy. Przechowuj każdy element jako własny klucz zamiast w większym obiekcie, by móc pobierać uporządkowane dane bezpłatnie pobierania pliku binarnego.

Podobnie jak w przypadku większości sprawdzonych metod, nie jest to zasada „wszystko albo nic”. W sytuacjach, w których nie można podzielić obiektu stanu i zapisać tylko minimalnego zestawu zmian, należy podzielić dane na podrzędne drzewa i zapisać je tylko na podstawie tych danych. W ten sposób zawsze najlepiej zapisywać całe drzewo stanu. Lepiej drobne ulepszenia, niż wcale nie.

Pamiętaj też, aby zawsze mierzyć wpływ na wydajność pisanego kodu. Chociaż to prawda, że małe zapisy w IndexedDB są skuteczniejsze niż duże zapisy, ma to znaczenie tylko wtedy, gdy zapisy w IndexedDB przez aplikację prowadzą do długich zadań, które blokują główny wątek i pogarszają komfort użytkowników. Pomiary pozwolą Ci zrozumieć, pod kątem czego optymalizujesz kampanię.

Podsumowanie

Deweloperzy mogą wykorzystać mechanizmy przechowywania danych klientów, takie jak IndexedDB, aby poprawić wrażenia użytkowników korzystających z aplikacji, nie tylko dzięki zachowywaniu stanu między sesjami, ale także skróceniu czasu wczytywania stanu początkowego przy kolejnych wizytach.

Prawidłowe używanie IndexedDB może znacznie zwiększyć komfort użytkowników, ale jego nieprawidłowe użycie lub niewłaściwa obsługa błędów może prowadzić do awarii aplikacji i niezadowolenia użytkowników.

Przechowywanie danych klienta zależy od wielu czynników, na które nie masz wpływu, dlatego ważne jest, aby Twój kod został dobrze przetestowany i prawidłowo radził sobie z błędami, nawet takimi, które na początku wydają się mało prawdopodobne.