Niektóre witryny mogą potrzebować komunikacji z serwerem roboczym bez konieczności informowania o wyniku. Oto przykłady:
- Strona wysyła do skryptu service workera listę adresów URL do wstępnego pobierania, aby po kliknięciu linku przez użytkownika dokumenty lub podresury strony były już dostępne w pamięci podręcznej, co znacznie przyspiesza nawigację.
- Strona prosi skrypt service worker o pobieranie i przechowywanie w pamięci podręcznej zestawu najpopularniejszych artykułów, aby były dostępne w trybie offline.
Delegowanie tego typu zadań niekrytycznych do usługi workera ma tę zaletę, że pozwala zwolnić wątek główny, aby lepiej obsługiwać pilniejsze zadania, takie jak reagowanie na interakcje użytkownika.
Z tego przewodnika dowiesz się, jak wdrożyć technikę jednokierunkowej komunikacji między stroną a mechanizmem Service Worker, wykorzystując standardowe interfejsy API przeglądarki i bibliotekę Workbox. Tego typu przypadki użycia nazywamy imperatywnym buforowaniem.
Przypadek środowisk produkcyjnych
Firma 1-800-Flowers.com wdrożyła wymuszone buforowanie (wstępne pobieranie) za pomocą usług działających w tle za pomocą postMessage()
, aby wstępnie pobierać najpopularniejsze produkty na stronach kategorii i przyspieszyć nawigację do stron ze szczegółami produktów.
Aby określić, które elementy mają być pobierane z poziomu pamięci podręcznej, używają podejścia mieszanego:
- Podczas wczytywania strony proszą usługę o pobieranie danych JSON dotyczących 9 najpopularniejszych elementów i dodawanie uzyskanych obiektów odpowiedzi do pamięci podręcznej.
- W przypadku pozostałych elementów są one wykrywane przez zdarzenie
mouseover
, dzięki czemu gdy użytkownik przesunie kursor na element, może wywołać pobranie zasobu na „żądanie”.
Do przechowywania odpowiedzi JSON używają interfejsu Cache API:
Gdy użytkownik kliknie produkt, powiązane z nim dane JSON zostaną pobrane z pamięci podręcznej bez konieczności łączenia się z siecią, co przyspieszy nawigację.
Korzystanie z Workbox
Workbox to łatwy sposób wysyłania wiadomości do service workera za pomocą pakietu workbox-window
, czyli zestawu modułów przeznaczonych do uruchamiania w kontekście okna. Uzupełniają one inne pakiety Workbox, które działają w usługach.
Aby przekazać stronie informacje do przekazania do skryptu service worker, najpierw pobierz odwołanie do obiektu Workbox do zarejestrowanego skryptu service worker:
const wb = new Workbox('/sw.js');
wb.register();
Następnie możesz bezpośrednio wysłać wiadomość w sposób deklaratywny, bez konieczności rejestracji, sprawdzania aktywacji czy zastanawiania się nad interfejsem API do komunikacji:
wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });
Usługa workera implementuje uchwyt message
, aby nasłuchiwać tych wiadomości. Może opcjonalnie zwrócić odpowiedź, ale w takich przypadkach nie jest to 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 spełnia Twoich potrzeb, oto jak za pomocą interfejsów API przeglądarki możesz zaimplementować okna komunikacji z usługą.
Interfejsu postMessage API można używać do ustanowienia jednokierunkowego mechanizmu komunikacji ze strony do workera usługi.
Strona wywołuje metodę postMessage()
w interfejsie skryptu service worker:
navigator.serviceWorker.controller.postMessage({
type: 'MSG_ID',
payload: 'some data to perform the task',
});
Usługa workera implementuje uchwyt message
, aby nasłuchiwać 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 (czyli „do wstępnego pobierania” lub „do opróżniania pamięci”). Na podstawie tego parametru usługa może się rozdzielać na różne ścieżki wykonania.
Jeśli operacja się powiedzie, użytkownik będzie mógł z niej korzystać. W przeciwnym razie nie zmieni to głównego przebiegu działania. Jeśli na przykład usługa 1-800-Flowers.com próbuje przechwycić dane, strona nie musi wiedzieć, czy udało się to service workerowi. Jeśli tak, użytkownik będzie mógł szybciej się poruszać. Jeśli tak nie jest, strona musi przejść do nowej strony. Potrwa to tylko trochę dłużej.
Prosty przykład wstępnego pobierania
Jednym z najczęstszych zastosowań obowiązkowego buforowania jest pobieranie zasobów z zapleczem, czyli pobieranie zasobów dla danego adresu URL przed tym, jak użytkownik przejdzie na tę stronę, aby przyspieszyć nawigację.
W witrynach można stosować różne metody wstępnego pobierania:
- Używanie tagów wstępnego pobierania linków na stronach: zasoby są przechowywane w pamięci podręcznej przeglądarki przez 5 minut, po czym mają zastosowanie normalne reguły
Cache-Control
dotyczące zasobu. - Uzupełnienie poprzedniej techniki o strategię buforowania w czasie działania w serwisie worker, aby wydłużyć czas istnienia zasobu z wyprzedzeniem poza ten limit.
W przypadku stosunkowo prostych scenariuszy wstępnego pobierania, takich jak wstępne pobieranie dokumentów lub konkretnych zasobów (JS, CSS itp.), te techniki są najlepszym rozwiązaniem.
Jeśli wymagana jest dodatkowa logika, na przykład analiza zasobu pobierania z wyprzedzeniem (plik JSON lub strony) w celu pobrania jego wewnętrznych adresów URL, lepiej jest w pełni przekazać to zadanie do mechanizmu pobierania z wyprzedzeniem.
Delegowanie tego typu operacji do usługi internetowej ma następujące zalety:
- Przeniesienie na pomocniczy wątek ciężkiego procesu pobierania i przetwarzania pobierania (którego opis znajdziesz poniżej) Dzięki temu główny wątek może zajmować się ważnymi zadaniami, takimi jak reagowanie na interakcje z użytkownikiem.
- Umożliwianie wielu klientom (np. zakładkom) ponownego używania wspólnej funkcji, a nawet wywoływania usługi jednocześnie bez blokowania wątku głównego.
Pobieranie wstępnie stron ze szczegółami produktu
Najpierw użyj metody postMessage()
w interfejsie mechanizmu 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 usługowym workerze zaimplementuj handler 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 wprowadziliśmy małą funkcję pomocniczą o nazwie fetchAsync()
, która iteruje tablicę adresów URL i wysyła żądanie pobierania dla 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 otrzymaniu odpowiedzi możesz polegać na nagłówkach pamięci podręcznej zasobu. W wielu przypadkach jednak, na przykład na stronach z informacjami o produkcie, 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 zmienić to zachowanie, przechowując pobrane zasoby w pamięci podręcznej usługi. Ma to dodatkową zaletę, ponieważ umożliwia udostępnianie pliku w scenariuszach offline.
Dane inne niż JSON
Gdy dane w formacie JSON zostaną pobrane z punktu końcowego serwera, często zawierają one inne adresy URL, które również warto pobrać w ramach wstępnego pobierania, 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 przejść przez listę produktów i zapisać w pamięci podręcznej obraz bohatera dla każdego 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);
}
}
W razie wystąpienia błędu 404 możesz też dodać obsługę wyjątków w tym kodzie. Jednak zaletą korzystania ze skryptu service worker do pobierania w poprzedniości jest to, że może on się nie powieść bez większych konsekwencji dla strony i wątku głównego. Możesz też zastosować bardziej złożoną logikę do post-processingu wstępnie pobranych treści, co zwiększy ich elastyczność i uwolni je od zależności od danych, z których korzystają. Nie ma żadnych ograniczeń.
Podsumowanie
W tym artykule omówiliśmy typowy przypadek użycia komunikacji jednokierunkowej między stroną a obsługą klienta: konieczne buforowanie. Omówione przykłady mają tylko na celu pokazanie jednego sposobu korzystania z tego wzorca. Tego samego podejścia można używać w innych przypadkach użycia, np. do buforowania najpopularniejszych artykułów na potrzeby offline, dodawania zakładek itp.
Więcej wzorów komunikacji między stroną a usługą:
- Rozgłaszanie aktualizacji: wywołanie strony z użyciem service workera w celu poinformowania o ważnych aktualizacjach (np. o dostępnej nowej wersji aplikacji internetowej).
- Komunikacja dwukierunkowa: delegowanie zadania do service workera (np. pobieranie dużych plików) i informowanie strony o postępach.