Strategie dotyczące buforowania skryptu service worker

Do tej pory w interfejsie Cache występowały tylko wzmianki i małe fragmenty kodu. Aby skutecznie korzystać z mechanizmów Service Worker, musisz zastosować co najmniej 1 strategię buforowania, co wymaga znajomości interfejsu Cache.

Strategia buforowania to interakcja między zdarzeniem fetch skryptu service worker a interfejsem Cache. Sposób napisania strategii buforowania zależy od tego, na przykład, że obsługa żądań zasobów statycznych może być inna niż obsługi dokumentów. Wpływa to na konstrukcję strategii buforowania.

Zanim przejdziemy do samych strategii, porozmawiajmy przez chwilę o tym, czym nie jest interfejs Cache i czym jest. Pokrótce przedstawimy niektóre dostępne w nim metody zarządzania pamięciami podręcznymi instancji service worker.

Interfejs Cache a pamięć podręczna HTTP

Jeśli nie masz doświadczenia z interfejsem Cache, może Cię kusić, by potraktować go jako funkcję przypominającą pamięć podręczną HTTP (lub przynajmniej z nią związaną z pamięci podręcznej HTTP). To nieprawda.

  • Interfejs Cache to mechanizm buforowania całkowicie niezależny od pamięci podręcznej HTTP.
  • Niezależnie od wybranej konfiguracji Cache-Control, która wpływa na pamięć podręczną HTTP, nie ma wpływu na to, jakie zasoby są przechowywane w interfejsie Cache.

Pamięć podręczna przeglądarki jest traktowana jako warstwowa. Pamięć podręczna HTTP to niskopoziomowa pamięć podręczna na podstawie par klucz-wartość i dyrektyw wyrażonych w nagłówkach HTTP.

Z kolei interfejs Cache to pamięć podręczna wysokiego poziomu obsługiwana przez interfejs API JavaScript. Daje to większą elastyczność niż w przypadku stosunkowo prostych par klucz-wartość HTTP. To tylko połowa oferowanej strategii buforowania. Oto kilka ważnych metod API związanych z pamięciami podręcznymi instancji Service Worker:

  • CacheStorage.open, aby utworzyć nową instancję Cache.
  • Cache.add i Cache.put, aby przechowywać odpowiedzi sieci w pamięci podręcznej skryptu service worker.
  • Cache.match, aby zlokalizować odpowiedź z pamięci podręcznej w instancji Cache.
  • Cache.delete, aby usunąć odpowiedź z pamięci podręcznej z instancji Cache.

Oto niektóre spośród nich. Są też inne przydatne metody, ale te podstawowe, które omówimy w dalszej części tego przewodnika.

Niepozorne wydarzenie fetch

Druga połowa strategii buforowania to zdarzenie fetch skryptu service worker. W tej dokumentacji słyszeliście już trochę o „przechwytywaniu żądań sieciowych”, a wtedy występuje zdarzenie fetch wewnątrz skryptu service worker:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(cacheName));
});

self.addEventListener('fetch', async (event) => {
  // Is this a request for an image?
  if (event.request.destination === 'image') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Respond with the image from the cache or from the network
      return cache.match(event.request).then((cachedResponse) => {
        return cachedResponse || fetch(event.request.url).then((fetchedResponse) => {
          // Add the network response to the cache for future visits.
          // Note: we need to make a copy of the response to save it in
          // the cache and use the original as the request response.
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

Jest to przykład zabawki – przykładowy, który możesz zobaczyć w działaniu – ale pokazuje, co mogą zrobić pracownicy service worker. Powyższy kod wykonuje te działania:

  1. Sprawdź właściwość destination żądania, aby się upewnić, że to żądanie dotyczące obrazu.
  2. Jeśli obraz znajduje się w pamięci podręcznej skryptu service worker, wyświetl go stamtąd. W przeciwnym razie pobierz obraz z sieci, zapisz odpowiedź w pamięci podręcznej i zwróć odpowiedź sieci.
  3. Wszystkie inne żądania są przekazywane przez skrypt service worker bez interakcji z pamięcią podręczną.

Obiekt event pobierania zawiera właściwość request, która zawiera przydatne informacje pomagające zidentyfikować typ każdego żądania:

  • url, czyli adres URL żądania sieciowego obsługiwanego obecnie przez zdarzenie fetch.
  • method, który jest metodą żądania (np. GET lub POST).
  • mode, w którym opisano tryb żądania. Wartość 'navigate' jest często używana do odróżniania żądań dokumentów HTML od innych żądań.
  • destination, który opisuje typ żądanej treści w sposób uniemożliwiający użycie rozszerzenia pliku żądanego zasobu.

Asynchronicznie to nazwa gry. Przypomnisz sobie, że zdarzenie install udostępnia metodę event.waitUntil, która przyjmuje obietnicę, i czeka na rozwiązanie problemu, zanim przejdzie do aktywacji. Zdarzenie fetch udostępnia podobną metodę event.respondWith, której można używać do zwracania wyniku asynchronicznego żądania fetch lub odpowiedzi zwracanej przez metodę match interfejsu Cache.

Strategie buforowania

Po zapoznaniu się z instancjami Cache i modułem obsługi zdarzeń fetch możesz przejść do strategii buforowania skryptów service worker. Możliwości są praktycznie nieograniczone, ale w tym przewodniku znajdziesz informacje o strategiach stosowanej w Workbox. Dzięki temu zorientujesz się, co dzieje się w wewnętrznym panelu tej usługi.

Tylko pamięć podręczna

Pokazuje przepływ ze strony do skryptu service worker, do pamięci podręcznej.

Zacznijmy od prostej strategii buforowania, którą nosimy „Tylko pamięć podręczna”. Chodzi po prostu o to: gdy skrypt service worker kontroluje stronę, pasujące żądania trafiają tylko do pamięci podręcznej. Oznacza to, że aby wzorzec działał prawidłowo, zasoby przechowywane w pamięci podręcznej muszą być wstępnie zapisane w pamięci podręcznej. Zasoby te nie będą aktualizowane w pamięci podręcznej, dopóki skrypt service worker nie zostanie zaktualizowany.

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

// Assets to precache
const precachedAssets = [
  '/possum1.jpg',
  '/possum2.jpg',
  '/possum3.jpg',
  '/possum4.jpg'
];

self.addEventListener('install', (event) => {
  // Precache assets on install
  event.waitUntil(caches.open(cacheName).then((cache) => {
    return cache.addAll(precachedAssets);
  }));
});

self.addEventListener('fetch', (event) => {
  // Is this one of our precached assets?
  const url = new URL(event.request.url);
  const isPrecachedRequest = precachedAssets.includes(url.pathname);

  if (isPrecachedRequest) {
    // Grab the precached asset from the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request.url);
    }));
  } else {
    // Go to the network
    return;
  }
});

Powyżej tablica zasobów jest wczytywana w pamięci podręcznej w momencie instalacji. Gdy skrypt service worker obsługuje pobieranie, sprawdzamy, czy URL żądania obsługiwany przez zdarzenie fetch znajduje się w tablicy zasobów wstępnie zapisanych w pamięci podręcznej. Jeśli tak, pobieramy zasób z pamięci podręcznej i pomijamy sieć. Inne żądania przechodzą do sieci i tylko do sieci. Aby zobaczyć, jak działa ta strategia, obejrzyj tę prezentację z otwartą konsolą.

Tylko sieć

Pokazuje przepływ ze strony, do skryptu service worker, do sieci.

Przeciwieństwem ustawienia „Tylko pamięć podręczna” jest ustawienie „Tylko sieć”, gdzie żądanie jest przekazywane przez skrypt service worker do sieci bez interakcji z pamięcią podręczną skryptu. To dobra strategia, aby zadbać o aktualność treści (podaj w nich znaczniki), ale pamiętaj, że nigdy nie sprawdzi się ona, gdy użytkownik jest offline.

Zadbaj o to, aby żądanie dotarło do sieci, oznacza to, że w odpowiedzi na żądanie nie wywołujesz funkcji event.respondWith. Jeśli chcesz przekazać treści jednoznacznie, możesz kliknąć puste return; w wywołaniu zwrotnym zdarzenia fetch w przypadku żądań, które chcesz przekazać do sieci. To właśnie dzieje się w wersji demonstracyjnej strategii „Tylko pamięć podręczna” w przypadku żądań, które nie są wstępnie przechowywane w pamięci podręcznej.

Najpierw pamięć podręczna, potem powrót do sieci

Pokazuje przepływ ze strony do skryptu service worker, do pamięci podręcznej, a następnie do sieci, jeśli nie jest w pamięci podręcznej.

W tej strategii wszystko zaczyna się jeszcze bardziej przykręcać. W przypadku dopasowanych żądań procedura wygląda tak:

  1. Żądanie trafia do pamięci podręcznej. Jeśli zasób znajduje się w pamięci podręcznej, należy go wyświetlić.
  2. Jeśli żądania nie ma w pamięci podręcznej, przejdź do sieci.
  3. Gdy żądanie sieciowe zostanie wykonane, dodaj je do pamięci podręcznej, a potem zwróć odpowiedź z sieci.

Oto przykład tej strategii, który możesz przetestować w prezentacji na żywo:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a request for an image
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the cache first
      return cache.match(event.request.url).then((cachedResponse) => {
        // Return a cached response if we have one
        if (cachedResponse) {
          return cachedResponse;
        }

        // Otherwise, hit the network
        return fetch(event.request).then((fetchedResponse) => {
          // Add the network response to the cache for later visits
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

Ten przykład dotyczy tylko obrazów, ale w szczególności dotyczy to wszystkich zasobów statycznych (takich jak pliki CSS, JavaScript, obrazy i czcionki), a zwłaszcza tych z napisem „#”. Pozwala ona przyspieszyć działanie zasobów, których nie można zmienić, ponieważ pomijanie kontroli aktualności treści na serwerze, które może uruchomić się z pamięci podręcznej HTTP. Co ważne, wszystkie zasoby przechowywane w pamięci podręcznej będą dostępne offline.

Najpierw sieć, powrót do pamięci podręcznej

Pokazuje przepływ ze strony, do skryptu service worker, do sieci, a następnie do pamięci podręcznej, jeśli sieć jest niedostępna.

Jeśli zmienisz zdanie „Najpierw pamięć podręczna, potem sieć”, doprowadzimy do strategii „Najpierw sieć, potem pamięć podręczna”, która brzmi tak:

  1. Przechodzisz najpierw do sieci z żądaniem, a odpowiedź umieszczasz w pamięci podręcznej.
  2. Jeśli później będziesz offline, korzystasz z najnowszej wersji odpowiedzi w pamięci podręcznej.

Ta strategia sprawdza się w przypadku żądań HTML lub interfejsu API, gdy chcesz mieć dostęp offline do najnowszej wersji zasobu, a jednocześnie zapewnić dostęp offline do najnowszej dostępnej wersji. Tak może to wyglądać po zastosowaniu do żądań kodu HTML:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a navigation request
  if (event.request.mode === 'navigate') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the network first
      return fetch(event.request.url).then((fetchedResponse) => {
        cache.put(event.request, fetchedResponse.clone());

        return fetchedResponse;
      }).catch(() => {
        // If the network is unavailable, get
        return cache.match(event.request.url);
      });
    }));
  } else {
    return;
  }
});

Możesz wypróbować tę funkcję w wersji demonstracyjnej. Najpierw wejdź na stronę. Umieszczenie odpowiedzi HTML w pamięci podręcznej może wymagać ponownego załadowania. Następnie w narzędziach dla programistów przeprowadź symulację połączenia offline i ponownie wczytaj stronę. Ostatnia dostępna wersja zostanie natychmiast wyświetlona z pamięci podręcznej.

W sytuacjach, gdy możliwość działania w trybie offline jest ważna, ale trzeba ją równoważyć z dostępem do najnowszej wersji znaczników lub danych interfejsu API, „najważniejsza jest sieć, pamięć podręczna po drugie” jest solidną strategią osiągania tego celu.

Nieaktualny podczas ponownej weryfikacji

Pokazuje przepływ ze strony do skryptu service worker, do pamięci podręcznej, a następnie z sieci do pamięci podręcznej.

Ze strategii omówionych do tej pory najbardziej skomplikowana jest opcja „Nieaktywny w trakcie ponownej weryfikacji”. Pod kilkoma względami przypomina ona ostatnie 2 strategie, ale nadaje mu priorytet szybkość dostępu do zasobu, jednocześnie dbając o jego aktualność w tle. Wygląda to mniej więcej tak:

  1. Przy pierwszym żądaniu zasobu pobierz go z sieci, umieść w pamięci podręcznej i zwróć odpowiedź sieci.
  2. W przypadku kolejnych żądań najpierw wyświetlaj zasób z pamięci podręcznej, a następnie „w tle”, wysyłaj go ponownie do sieci i aktualizuj jego wpis w pamięci podręcznej.
  3. W przypadku kolejnych żądań otrzymasz ostatnią wersję pobraną z sieci, która została umieszczona w pamięci podręcznej w poprzednim kroku.

To świetna strategia w przypadku rzeczy, które są dość ważne, ale nie muszą być aktualne. Na stronie mediów społecznościowych mogą być np. awatary. Są aktualizowane, gdy tylko użytkownicy otworzą aplikację, ale najnowsza wersja nie jest wymagana do każdego żądania.

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request).then((cachedResponse) => {
        const fetchedResponse = fetch(event.request).then((networkResponse) => {
          cache.put(event.request, networkResponse.clone());

          return networkResponse;
        });

        return cachedResponse || fetchedResponse;
      });
    }));
  } else {
    return;
  }
});

Możesz zobaczyć, jak to działa w kolejnej prezentacji na żywo, zwłaszcza jeśli zwracasz uwagę na kartę Sieć w narzędziach dla programistów w przeglądarce oraz jej przeglądarkę CacheStorage (o ile są one dostępne w narzędziach dla programistów w przeglądarce).

Ruszaj do skrzynki roboczej!

Ten dokument stanowi podsumowanie naszego przeglądu interfejsu API instancji Service Worker oraz powiązanych z nim interfejsów API. Oznacza to, że już wiesz już wystarczająco dużo o sposobach bezpośredniego wykorzystywania mechanizmów Service Worker, aby zacząć majsterkować przy użyciu Workbox.