Sprawdzone metody korzystania z IndexedDB

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

Gdy użytkownik po raz pierwszy wczytuje witrynę lub aplikację, tworzenie początkowego stanu aplikacji używanego do renderowania UI wymaga wykonania wielu zadań. Czasami aplikacja musi na przykład uwierzytelnić się po stronie klienta, a następnie wysłać kilka żądań do interfejsu API, zanim uzyska wszystkie dane potrzebne do wyświetlenia strony.

Przechowywanie stanu aplikacji w IndexedDB może być świetnym sposobem na przyspieszenie czasu wczytywania w przypadku ponownych wizyt. Aplikacja może następnie synchronizować się z dowolnymi usługami interfejsu API w tle i okresowo uzupełniać interfejs o nowe dane, stosując strategię nieaktualnej weryfikacji poprawności.

Innym przydatnym zastosowaniem IndexedDB jest przechowywanie treści użytkowników w formie tymczasowej przed przesłaniem na serwer lub jako pamięć podręczna ze zdalnymi danymi po stronie klienta – lub oczywiście w obu tych miejscach.

W przypadku korzystania z IndexedDB trzeba jednak wziąć pod uwagę wiele ważnych kwestii, które mogą nie być od razu oczywiste dla programistów, którzy dopiero zaczynają korzystać z interfejsów API. Ten artykuł zawiera odpowiedzi na najczęstsze pytania i omawia najważniejsze kwestie, o których należy pamiętać przy utrwalaniu danych w IndexedDB.

Zapewnianie przewidywalności działania aplikacji

Wiele zawiłości związanych z IndexedDB wynika z faktu, że jest tak wiele czynników, nad którymi Ty (deweloper) nie masz kontroli. W tej sekcji omawiamy wiele problemów, o których musisz pamiętać podczas pracy z IndexedDB.

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

Jeśli przechowujesz duże pliki użytkowników, takie jak obrazy czy filmy, możesz spróbować je przechowywać jako obiekty File lub Blob. Na niektórych platformach będzie to działać, a na innych nie. Safari w systemie iOS nie może przechowywać w IndexedDB bazy danych Blob.

Na szczęście przekształcenie obiektu Blob w ArrayBuffer i odwrotnie nie jest zbyt trudne. Przechowywanie ArrayBuffer w IndexedDB jest bardzo obsługiwane.

Pamiętaj jednak, że Blob ma typ MIME, podczas gdy ArrayBuffer nie. Aby prawidłowo przeprowadzić konwersję, musisz zapisać ten typ razem z buforem.

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

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

Drugi kierunek jest nieco bardziej skomplikowany i jest procesem asynchronicznym. Do odczytu bloba jako typu ArrayBuffer możesz użyć obiektu FileReader. Po zakończeniu odczytu czytnik wywołuje zdarzenie loadend. Możesz zakończyć ten proces formatem Promise w ten 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ą one poza Twoją kontrolą jako deweloper. Na przykład niektóre przeglądarki obecnie nie zezwalają na zapis w IndexedDB w trybie przeglądania prywatnego. Istnieje również ryzyko, że użytkownik korzysta z urządzenia, na którym kończy się miejsce na dysku. Przeglądarka nie pozwala więc na zapisywanie żadnych danych.

Z tego powodu niezwykle ważne jest zapewnienie prawidłowej obsługi błędów w kodzie IndexedDB. Oznacza to również, że dobrze jest też zachować stan aplikacji w pamięci (oprócz jej zapisania), dzięki czemu interfejs użytkownika nie przestanie działać w trybie przeglądania prywatnego lub gdy brakuje miejsca na dane (nawet jeśli niektóre inne funkcje aplikacji, które wymagają miejsca na dane, nie będą działać).

Możesz wychwytywać błędy w operacjach IndexedDB, dodając moduł obsługi zdarzeń error za każdym razem, gdy tworzysz obiekt IDBDatabase, IDBTransaction lub IDBRequest.

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

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

W odróżnieniu od baz danych po stronie serwera, do których można ograniczyć nieupoważniony 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ż modyfikowanie danych przechowywanych lokalnie czasami jest nietypowe, użytkownicy często je usuwają. Ważne jest, aby aplikacja obsługiwała oba te przypadki bez napotykania błędów.

Zapisane dane mogą być nieaktualne

Podobnie jak w przypadku poprzedniej sekcji, nawet jeśli użytkownik sam nie zmodyfikował danych, możliwe, że dane, które przechowuje, zostały zapisane w starej wersji kodu, prawdopodobnie w wersji zawierającej błędy.

Platforma IndexedDB ma wbudowaną obsługę wersji schematu i uaktualniania jej za pomocą metody IDBOpenDBRequest.onupgradeneeded(). Nadal jednak musisz napisać kod uaktualnienia w taki sposób, aby obsługiwał użytkownika pochodzącego z poprzedniej wersji (w tym wersji z błędem).

Bardzo pomocne mogą być w tym przypadku testy jednostkowe, ponieważ często nie jest możliwe ręczne przetestowanie wszystkich możliwych ścieżek i przypadków uaktualnienia.

Utrzymywanie wydajności aplikacji

Jedną z najważniejszych funkcji IndexedDB jest asynchroniczny interfejs API. Nie daj się jednak zmylić, uznając, że podczas korzystania nie musisz martwić się o wydajność. W wielu przypadkach niewłaściwe użycie może spowodować zablokowanie wątku głównego, co może doprowadzić do zacinania się i braku odpowiedzi.

Zgodnie z ogólną zasadą odczyty i zapisy w IndexedDB nie powinny być większe niż jest to wymagane do uzyskania dostępu do danych.

Chociaż IndexedDB umożliwia przechowywanie dużych, zagnieżdżonych obiektów w jednym rekordzie (co jest dość wygodne dla programistów), należy unikać takiej sytuacji. Wynika to z tego, że gdy IndexedDB przechowuje obiekt, musi najpierw utworzyć jego uporządkowaną kopię, a proces klonowania uporządkowanych danych odbywa się w wątku głównym. Im większy obiekt, tym dłuższy czas blokowania.

Stanowi to pewne problemy przy planowaniu utrzymywania stanu aplikacji w IndexedDB, ponieważ większość popularnych bibliotek zarządzania stanem (np. Redux) zarządza całym drzewem stanu jako jednym obiektem JavaScript.

Ten sposób zarządzania stanem ma wiele korzyści (np. ułatwia analizowanie kodu i debugowanie), a chociaż przechowywanie całego drzewa stanu w postaci pojedynczego rekordu w IndexedDB jako pojedynczy rekord w IndexedDB może być kuszące i wygodne, ale po każdej zmianie (nawet ograniczonej/odrzucanej) niepotrzebnie blokuje się wątek główny, a nawet zwiększa prawdopodobieństwo wystąpienia błędów zapisu lub błędów w przeglądarce.

Zamiast przechowywać całe drzewo stanu w jednym rekordzie, podziel go na kilka rekordów i aktualizuj tylko te, które faktycznie się zmienią.

To samo dotyczy przechowywania w IndexedDB dużych elementów, takich jak obrazy, muzyka czy filmy. Każdy element należy przechowywać własnym kluczem, a nie wewnątrz większego obiektu. Dzięki temu można pobierać uporządkowane dane bez konieczności płacenia za pobranie pliku binarnego.

Podobnie jak w przypadku większości sprawdzonych metod, nie jest to reguła „wszystko albo nic”. W sytuacjach, gdy podział obiektu stanu nie jest możliwy i po prostu zapisujesz minimalny zestaw zmian, podzielenie danych na poddrzewa i zapisanie tylko tych elementów jest nadal zalecane, ponieważ pozwala zawsze zapisywać całe drzewo stanu. Małe poprawki są lepsze niż brak ich w ogóle.

Pamiętaj też, aby zawsze mierzyć wpływ na wydajność pisanego kodu. Choć to prawda, że małe zapisy w IndexedDB będą działać lepiej niż duże zapisy, ma to znaczenie tylko wtedy, gdy zapisy wykonywane przez Twoją aplikację w IndexedDB faktycznie prowadzą do długich zadań, które blokują wątek główny i pogarszają wrażenia użytkownika. Pomiary są bardzo ważne, aby ustalić, pod kątem czego optymalizujesz kampanię.

Podsumowanie

Deweloperzy mogą korzystać z mechanizmów przechowywania danych klientów, takich jak IndexedDB, aby poprawić wrażenia użytkowników aplikacji dzięki nie tylko utrzymywaniu stanu w kolejnych sesjach, ale też skróceniu czasu potrzebnego na ładowanie stanu początkowego przy kolejnych wizytach.

Właściwe korzystanie z IndexedDB może znacznie poprawić wrażenia użytkowników, ale nieprawidłowe korzystanie z niej lub nieudane radzenie sobie z przypadkami błędów może prowadzić do awarii aplikacji i niezadowolenia użytkowników.

Ponieważ na przechowywanie danych klientów ma wpływ wiele czynników, na które nie masz wpływu, ważne jest, aby Twój kod był dobrze przetestowany i prawidłowo radził sobie z błędami – nawet te, które na początku wydają się mało prawdopodobne.