Przewodnik na temat imperatywnego buforowania

Andrew Guan
Andrew Guan

Niektóre witryny mogą wymagać komunikacji z skryptem service worker bez konieczności informowania o wyniku. Oto przykłady:

  • Strona wysyła do skryptu service worker listę adresów URL, które mają być pobierane z wyprzedzeniem. Gdy użytkownik kliknie link, zasoby podrzędne dokumentu lub strony są już dostępne w pamięci podręcznej, co znacznie przyspiesza nawigację.
  • Strona zawiera prośbę o pobranie zestawu najważniejszych artykułów i ich zapisanie w pamięci podręcznej w celu umożliwienia ich wykorzystania w trybie offline.

Przekazywanie tych typów niekrytycznych zadań do skryptu service worker pozwala zwolnić wątek główny i ułatwia obsługę bardziej palących zadań, takich jak reagowanie na interakcje użytkownika.

Diagram strony z żądaniem zasobów do pamięci podręcznej w mechanizmie Service Worker.

W tym przewodniku pokażemy, jak wdrożyć jednokierunkową technikę komunikacji między stroną a skryptem service worker przy użyciu standardowych interfejsów API przeglądarki i biblioteki Workbox. Takie przypadki użycia nazywamy imperatywnym buforowaniem.

Przypadek produkcyjny

W witrynie 1-800-Flowers.com zaimplementowano imperatywne buforowanie (pobieranie z wyprzedzeniem) z skryptami service worker przez postMessage(), aby z wyprzedzeniem pobierać najpopularniejsze elementy na stronach kategorii i przyspieszyć poruszanie się po stronach z informacjami o produktach.

Logo 1-800 kwiatów.

Decyduje o tym, które elementy pobrać z wyprzedzeniem, używając różnych metod:

  • Podczas wczytywania strony instancja robocza Servicer pobiera dane JSON dla 9 pierwszych elementów i dodaje wynikowe obiekty odpowiedzi do pamięci podręcznej.
  • W przypadku pozostałych elementów nasłuchują zdarzenia mouseover , więc gdy użytkownik przesunie kursor na element, może uruchomić pobieranie zasobu na „żądanie”.

Do przechowywania odpowiedzi JSON używają interfejsu Cache API:

Logo 1-800 kwiatów.
Pobieranie z wyprzedzeniem danych produktów w formacie JSON ze stron z informacjami o produktach w domenie 1-800Flowers.com.

Gdy użytkownik klika element, powiązane z nim dane JSON mogą zostać pobrane z pamięci podręcznej bez konieczności łączenia się z siecią, co przyspiesza nawigację.

Korzystanie z Workbox

Pole robocze umożliwia łatwe wysyłanie wiadomości do skryptu service worker za pomocą pakietu workbox-window – zestawu modułów uruchamianych w kontekście okna. Stanowią one uzupełnienie innych pakietów Workspace działających w skrypcie service worker.

Aby skomunikować stronę z skryptem service worker, najpierw uzyskaj odniesienie do obiektu Workbox do zarejestrowanego skryptu service worker:

const wb = new Workbox('/sw.js');
wb.register();

Następnie możesz wysłać wiadomość bezpośrednio deklaratywnie, bez potrzeby procesu rejestracji, sprawdzania aktywacji czy rozważania interfejsu API do komunikacji:

wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });

Skrypt service worker implementuje moduł obsługi message w celu odsłuchiwania tych wiadomości. Może opcjonalnie zwrócić odpowiedź, ale w takich przypadkach nie jest konieczne:

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PREFETCH') {
    // do something
  }
});

Korzystanie z interfejsów API przeglądarek

Jeśli biblioteka Workbox nie zaspokaja Twoich potrzeb, poniżej znajdziesz sposób, w jaki możesz wdrożyć okno komunikacji między instancjami roboczymi, korzystając z interfejsów API przeglądarki.

Interfejs postMessage API może służyć do ustanowienia jednokierunkowego mechanizmu komunikacji ze strony do skryptu service worker.

Strona wywołuje postMessage() w interfejsie skryptu service worker:

navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
  payload: 'some data to perform the task',
});

Skrypt service worker implementuje moduł obsługi message w celu odsłuchiwania tych wiadomości.

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === MSG_ID) {
    // do something
  }
});

Atrybut {type : 'MSG_ID'} nie jest absolutnie wymagany, ale jest jednym ze sposobów na umożliwienie stronie wysyłania różnych typów instrukcji do skryptu service worker (tzn. pobierania z wyprzedzeniem i czyszczenia miejsca na dane). Na podstawie tej flagi skrypt service worker może rozgałęziać się na różne ścieżki wykonania.

Jeśli operacja się uda, użytkownik będzie mógł czerpać korzyści z tej funkcji, ale jeśli nie, nie wpłynie to na główny przepływ pracy użytkownika. Jeśli na przykład 1-800-Flowers.com próbuje wstępnie buforować, strona nie musi wiedzieć, czy skrypt service worker się udał. Dzięki temu nawigacja będzie szybsza. Jeśli tak się nie stanie, musisz przejść na nową stronę. Potrwa to tylko trochę dłużej.

Prosty przykład pobierania z wyprzedzeniem

Jedną z najczęstszych zastosowań imperatywnego buforowania jest pobieranie z wyprzedzeniem, czyli pobieranie zasobów z danego adresu URL, zanim użytkownik przejdzie pod ten adres, w celu przyspieszenia nawigacji.

Są różne sposoby implementacji pobierania z wyprzedzeniem w witrynach:

W przypadku stosunkowo prostych scenariuszy pobierania z wyprzedzeniem, np. pobierania z wyprzedzeniem dokumentów lub określonych zasobów (JS, CSS itp.), te techniki są najlepszym rozwiązaniem.

Jeśli wymagana jest dodatkowa logika, na przykład podczas analizowania zasobu pobierania z wyprzedzeniem (pliku lub strony JSON) w celu pobrania jego wewnętrznych adresów URL, bardziej odpowiednie jest przekazanie tego zadania w całości do skryptu service worker.

Przekazywanie tych typów operacji do skryptu service worker ma te zalety:

  • Przeniesienie ciężkiej pracy związanej z pobieraniem i przetwarzaniem pobierania (które zostanie wprowadzone później) do wątku dodatkowego. W ten sposób uwolni on główny wątek i będzie on mógł zająć się ważniejszymi zadaniami, takimi jak reagowanie na interakcje użytkowników.
  • Umożliwienie wielu klientom (np. kart) ponownego korzystania z tych samych funkcji, a nawet jednoczesne wywoływanie usługi bez blokowania wątku głównego.

Pobieraj z wyprzedzeniem strony ze szczegółami produktów

Najpierw użyj postMessage() w interfejsie skryptu service worker i przekaż tablicę adresów URL do pamięci podręcznej:

navigator.serviceWorker.controller.postMessage({
  type: 'PREFETCH',
  payload: {
    urls: [
      'www.exmaple.com/apis/data_1.json',
      'www.exmaple.com/apis/data_2.json',
    ],
  },
});

W skrypcie service worker zaimplementuj moduł obsługi message, aby przechwytywać i przetwarzać wiadomości wysyłane przez dowolną aktywną kartę:

addEventListener('message', (event) => {
  let data = event.data;
  if (data && data.type === 'PREFETCH') {
    let urls = data.payload.urls;
    for (let i in urls) {
      fetchAsync(urls[i]);
    }
  }
});

W poprzednim kodzie przedstawiliśmy małą funkcję pomocniczą o nazwie fetchAsync(), która pozwala iterować tablicę adresów URL i wysyłać żądanie pobierania do każdego z nich:

async function fetchAsync(url) {
  // await response of fetch call
  let prefetched = await fetch(url);
  // (optionally) cache resources in the service worker storage
}

Po uzyskaniu odpowiedzi możesz polegać na nagłówkach buforowania zasobu. Jednak w wielu przypadkach, na przykład na stronach ze szczegółami produktu, zasoby nie są przechowywane w pamięci podręcznej (co oznacza, że mają nagłówek Cache-control o wartości no-cache). W takich przypadkach możesz zastąpić to działanie, przechowując pobrany zasób w pamięci podręcznej skryptu service worker. Dodatkową zaletą jest możliwość wyświetlania pliku w trybie offline.

Więcej niż dane JSON

Po pobraniu danych JSON z punktu końcowego serwera często zawierają one inne adresy URL, które również warto pobrać z wyprzedzeniem, np. obraz lub inne dane punktu końcowego powiązane z tymi danymi pierwszego poziomu.

Załóżmy, że w naszym przykładzie zwracane dane JSON to informacje o witrynie sklepu spożywczego:

{
  "productName": "banana",
  "productPic": "https://cdn.example.com/product_images/banana.jpeg",
  "unitPrice": "1.99"
 }

Zmodyfikuj kod fetchAsync(), aby iterować listę produktów i zachować baner powitalny dla każdej z nich:

async function fetchAsync(url, postProcess) {
  // await response of fetch call
  let prefetched = await fetch(url);

  //(optionally) cache resource in the service worker cache

  // carry out the post fetch process if supplied
  if (postProcess) {
    await postProcess(prefetched);
  }
}

async function postProcess(prefetched) {
  let productJson = await prefetched.json();
  if (productJson && productJson.product_pic) {
    fetchAsync(productJson.product_pic);
  }
}

Możesz dodać obsługę wyjątków dla tego kodu w sytuacjach takich jak błąd 404. Zaletą stosowania mechanizmu pobierania z wyprzedzeniem jest to, że może ono zawieść bez wielu konsekwencji dla strony i wątku głównego. Podczas przetwarzania pobranych z wyprzedzeniem treści możesz też używać bardziej rozbudowanych logiki, co zwiększy jej elastyczność i brak powiązania z przetwarzanymi danymi. Nie ma żadnych ograniczeń.

Podsumowanie

W tym artykule omówiliśmy typowy przypadek użycia jednokierunkowej komunikacji między stroną a skryptą service worker, czyli imperatywne buforowanie. Przedstawione przykłady pokazują tylko jeden sposób wykorzystania tego wzorca. To samo podejście można zastosować również do innych zastosowań, np. zapisywania najpopularniejszych artykułów na żądanie do przeglądania offline, dodawania zakładek itp.

Więcej wzorców komunikacji między stronami i skryptami service worker znajdziesz w tych dokumentach:

  • Transmitowanie aktualizacji: wywołanie strony z skryptu service worker w celu poinformowania o ważnych aktualizacjach (np. że jest dostępna nowa wersja aplikacji internetowej).
  • Dwukierunkowa komunikacja: przekazywanie zadania skryptowi service worker (np. bardzo intensywne pobieranie) i informowanie strony o postępach.