Jak myśleć o usługach w tle.
Roboty usługowe są potężne i zdecydowanie warto się ich nauczyć. Dzięki nim możesz zapewnić użytkownikom zupełnie nowe wrażenia. Witryna może wczytywać się natychmiast. Może działać offline. Można ją zainstalować jako aplikację na daną platformę, która będzie wyglądać i działać świetnie, ale jednocześnie będzie miała zasięg i swobodę internetu.
Jednak service worker różni się od tego, do czego przywykli programiści. Ich nauka jest trudna, a trzeba uważać na kilka pułapek.
Niedawno współpracowałem z Google Developers nad projektem Service Workies, czyli bezpłatną grą, która pomaga zrozumieć skrypty service worker. Podczas tworzenia i używania złożonych funkcji service workerów napotkałem na kilka problemów. Najbardziej pomogło mi wymyślanie metafor. W tym poście przyjrzymy się tym modelom mentalnym i spróbujemy zrozumieć paradoksalne cechy, które sprawiają, że pracownicy usług są jednocześnie trudni i świetni.
To samo, ale jednak inne
Podczas kodowania serwisowego workera wiele rzeczy może wydawać się znajomych. Możesz używać swoich ulubionych nowych funkcji języka JavaScript. Zdarzenia cyklu życia nasłuchujesz tak samo jak zdarzenia UI. Sterowanie przepływem danych odbywa się za pomocą obietnic.
Ale inne zachowania skryptu service worker powodują, że przeczesujesz zdezorientowany głową. Zwłaszcza wtedy, gdy po odświeżeniu strony nie widzisz wprowadzonych zmian w kodzie.
nowa warstwa,
Podczas tworzenia witryny zwykle trzeba się zajmować tylko 2 poziomami: klientem i serwerem. Usługa w tle to zupełnie nowa warstwa, która znajduje się pośrodku.
Skrypt service worker to rodzaj rozszerzenia do przeglądarki, które Twoja witryna może zainstalować w przeglądarce użytkownika. Po zainstalowaniu pracownik usługi rozszerza przeglądarkę Twojej witryny o potężną warstwę pośrednią. Ten poziom usługi może przechwytywać i przetwarzać wszystkie żądania wysyłane przez Twoją witrynę.
Warstwę usługi w ramach service workera ma własny cykl życia, niezależny od karty przeglądarki. Zwykłe odświeżenie strony nie wystarczy do zaktualizowania usługi w tle, tak jak nie oczekujesz, że odświeżenie strony zaktualizuje kod wdrożony na serwerze. Każda warstwa ma własne reguły aktualizacji.
W grze Service Workies omawiamy wiele szczegółów cyklu życia usługi i dajemy Ci mnóstwo okazji do pracy z usługami.
Potężne, ale ograniczone
Skrypt service worker w Twojej witrynie zapewnia niesamowite korzyści. Witryna może:
- działać bez zarzutu nawet wtedy, gdy użytkownik jest offline.
- znacząco zwiększyć wydajność dzięki buforowaniu.
- używać powiadomień push.
- być instalowana jako PWA;
Mimo że workery usługowe mogą wiele, są one ograniczone ze względu na swoją konstrukcję. Nie mogą wykonywać żadnych działań synchronicznie ani w tym samym wątku co Twoja witryna. Oznacza to, że nie masz dostępu do tych funkcji:
- localStorage
- DOM
- okno
Dobra wiadomość jest taka, że strona może komunikować się z usługą w kilka sposobów, m.in. bezpośrednio postMessage
, za pomocą kanałów wiadomości jeden-do-jednego i kanałów rozgłoszeniowych jeden-do-wielu.
długotrwałe, ale krótkotrwałe
Aktywny service worker działa nawet wtedy, gdy użytkownik opuści Twoją witrynę lub zamknie kartę. Przeglądarka zachowuje ten skrypt service worker, aby był gotowy, gdy użytkownik wróci do Twojej witryny. Zanim zostanie wysłane pierwsze żądanie, skrypt service worker może je przechwycić i przejąć kontrolę nad stroną. Dzięki temu witryna może działać w trybie offline – skrypt service worker może wyświetlić wersję strony z pamięci podręcznej, nawet jeśli użytkownik nie ma połączenia z internetem.
W usługach workerów tę koncepcję obrazuje Kolohe (przyjazny usługowiec), który przechwytuje żądania i je obsługuje.
Zatrzymano
Mimo że instancje robocze wydają się nieśmiertelne, można je zatrzymać niemal w każdej chwili. Przeglądarka nie chce marnować zasobów na skrypt service worker, który obecnie nic nie robi. Zatrzymanie nie jest tym samym co zakończenie – usługa robocza pozostaje zainstalowana i aktywna. Urządzenie jest tylko uśpione. Gdy będzie to potrzebne (np. do obsługi żądania), przeglądarka ponownie uruchomi proces.
waitUntil
Ze względu na ciągłą możliwość przejścia w tryb uśpienia Twój serwis worker musi mieć możliwość poinformowania przeglądarki, że wykonuje coś ważnego i nie chce zasnąć. Właśnie tutaj do akcji wkraczają event.waitUntil()
. Ta metoda wydłuża cykl życia, w którym jest używana, zapobiegając zarówno zatrzymaniu, jak i przechodzeniu do następnej fazy cyklu, dopóki nie będziemy gotowi. Daje nam to czas na skonfigurowanie pamięci podręcznej, pobranie zasobów z sieci itp.
W tym przykładzie informujemy przeglądarkę, że nasz serwis worker nie zakończy instalacji, dopóki nie zostanie utworzona i wypełniona pamięć podręczna assets
z obrazem miecza:
self.addEventListener("install", event => {
event.waitUntil(
caches.open("assets").then(cache => {
return cache.addAll(["/weapons/sword/blade.png"]);
})
);
});
Uwaga na stan globalny
Gdy nastąpi uruchomienie lub zatrzymanie, zasięg globalny pracownika usługi zostanie zresetowany. Uważaj, aby nie używać w usługach workera żadnych stanów globalnych, bo następnym razem, gdy usługa zostanie uruchomiona, może mieć stan inny od oczekiwanego.
Rozważ ten przykład, który używa stanu globalnego:
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
W przypadku każdego żądania ten proces roboczy zapisuje liczbę, np. 0.13981866382421893
. Zmienna hasHandledARequest
również zmieni się na true
. Teraz usługa nie wykonuje żadnych działań, więc przeglądarka ją zatrzymuje. Gdy następnym razem pojawi się żądanie, przeglądarka ponownie uruchomi pracownika usługi. Skrypt jest ponownie oceniany. Teraz hasHandledARequest
jest zresetowany do wartości false
, a favoriteNumber
ma wartość zupełnie inną – 0.5907281835659033
.
Nie możesz polegać na zapisanym stanie w skrypcie service worker. Tworzenie instancji takich elementów jak kanały wiadomości może też powodować błędy: za każdym razem, gdy usługa robocza przestanie działać lub się uruchomi, będziesz mieć nową instancję.
W rozdziale 3 książki „Service Workers” zatrzymany skrypt service worker traci kolor, gdy czeka na powiadomienie.
Razem, ale osobno
Twoja strona może być sterowana tylko przez 1 skrypt service worker naraz. Może jednak zainstalować jednocześnie 2 usługi. Gdy wprowadzisz zmiany w kodzie usługi i odświeżysz stronę, nie edytujesz w ogóle usługi. Usług nie można zmienić. Tworzysz zupełnie nową kampanię. Ten nowy serwis worker (nazwijmy go SW2) zostanie zainstalowany, ale nie zostanie jeszcze aktywowany. Musi wait, aż bieżący pracownik usługi (SW1) zakończy działanie (gdy użytkownik opuści witrynę).
modyfikowanie pamięci podręcznej innego service workera;
Podczas instalacji SW2 może skonfigurować niektóre elementy, zwykle tworząc i wypełniając pamięci podręczne. Pamiętaj jednak, że nowy serwis worker ma dostęp do wszystkiego, do czego ma dostęp obecny serwis worker. Jeśli nie będziesz uważać, nowy skrypt service worker w stanie oczekiwania może naprawdę namieszać w bieżącym skrypcie service worker. Oto kilka przykładów, które mogą spowodować problemy:
- SW2 może usunąć pamięć podręczną, z której aktywnie korzysta SW1.
- SW2 może edytować zawartość pamięci podręcznej, której używa SW1, co spowoduje, że SW1 odpowie komponentami, których strona nie oczekuje.
Pomiń skipWaiting
Skrypt service worker może też używać ryzykownej metody skipWaiting()
, aby przejąć kontrolę nad stroną zaraz po zakończeniu instalacji. Zazwyczaj nie jest to dobry pomysł, chyba że celowo chcesz zastąpić usługę workera, która ma błędy. Nowy serwis worker może używać zaktualizowanych zasobów, których nie oczekuje bieżąca strona, co może powodować błędy.
Zacznij od zera
Aby zapobiec wzajemnemu blokowaniu się przez service workery, upewnij się, że używają różnych pamięci podręcznych. Najprostszym sposobem jest utworzenie wersji nazw używanych przez nie nazw pamięci podręcznej.
const version = 1;
const assetCacheName = `assets-${version}`;
self.addEventListener("install", event => {
caches.open(assetCacheName).then(cache => {
// confidently do stuff with your very own cache
});
});
Gdy wdrożysz nowy skrypt service worker, zwiększysz wartość version
, aby wykonywał on swoje zadania w zupełnie oddzielnej pamięci podręcznej od poprzedniego skryptu service worker.
Sprzątanie po zakończeniu
Gdy usługa osiągnie stan activated
, wiesz, że przejęła kontrolę, a poprzednia usługa jest zbędna. W tym momencie ważne jest, aby uporządkować stare workery usług. Nie tylko uszanuje limity miejsca na dane w pamięci podręcznej użytkowników, ale też może zapobiegać niezamierzonym błędom.
Metoda caches.match()
to często używany skrót do pobierania elementu z dowolnej pamięci podręcznej, w której występuje dopasowanie. Przegląda jednak pamięci podręczne w kolejności ich utworzenia. Załóżmy, że masz 2 wersje pliku skryptu app.js
w 2 różnych pamięciach podręcznych – assets-1
i assets-2
. Strona oczekuje nowszego skryptu, który jest przechowywany w pliku assets-2
. Jeśli jednak nie usuniesz starej pamięci podręcznej, caches.match('app.js')
zwróci starą z assets-1
i najprawdopodobniej uszkodzi Twoją witrynę.
Aby wyczyścić poprzednich pracowników usługi, wystarczy usunąć wszystkie dane z poziomu pamięci podręcznej, których nie potrzebuje nowy pracownik usługi:
const version = 2;
const assetCacheName = `assets-${version}`;
self.addEventListener("activate", event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== assetCacheName){
return caches.delete(cacheName);
}
});
);
});
);
});
Zapobieganie kolizjom między skryptami service worker wymaga trochę pracy i dyscypliny, ale warto.
Sposób działania skryptów service worker
Aby zbudować zaufanie, musisz mieć odpowiednią postawę wobec pracowników obsługi klienta. Gdy opanujesz te narzędzia, będziesz mieć możliwość tworzenia niesamowitych doświadczeń dla użytkowników.
Jeśli chcesz to wszystko zrozumieć, grając w grę, masz szczęście! Graj w Service Workies, aby dowiedzieć się, jak za pomocą usług w tle pokonać potwory offline.