Cykl życia usługi jest najbardziej skomplikowaną częścią. Jeśli nie wiesz, czego chce od Ciebie Google i jakie korzyści Ci to przyniesie, możesz mieć wrażenie, że jest Twoim wrogiem. Gdy już poznasz działanie tej funkcji, możesz dostarczać użytkownikom płynne i nieinwazyjne aktualizacje, łącząc najlepsze wzorce internetowe i natywne.
To szczegółowy przewodnik, ale punkty na początku każdej sekcji zawierają większość informacji, które są Ci potrzebne.
Intencja
Celem cyklu życia jest:
- Uwzględnij tryb offline.
- Umożliw nowemu skryptowi service worker przygotowanie się do działania bez zakłócania obecnego.
- Upewnij się, że strona objęta zakresem jest kontrolowana przez ten sam skrypt service worker (lub żaden skrypt service worker) na całej stronie.
- Upewnij się, że w danym momencie działa tylko jedna wersja witryny.
Ten ostatni jest bardzo ważny. Bez usługowych workerów użytkownicy mogą wczytywać jedną kartę Twojej witryny, a potem otworzyć kolejną. Może to spowodować jednoczesne działanie 2 wersji witryny. Czasami jest to w porządku, ale jeśli masz do czynienia z pamięcią, możesz łatwo skończyć z 2 kartami, które mają bardzo różne opinie na temat tego, jak zarządzać współdzieloną pamięcią. Może to spowodować błędy lub, co gorsza, utratę danych.
Pierwszy skrypt service worker
W skrócie:
- Zdarzenie
install
to pierwsze zdarzenie, które otrzymuje usługa w ramach usługi w tle. Występuje tylko raz. - Obietnica przekazana do
installEvent.waitUntil()
sygnalizuje czas trwania i skuteczność instalacji. - Usługa wątek usługi nie będzie otrzymywać zdarzeń takich jak
fetch
ipush
, dopóki nie zakończy się jej instalacja i nie stanie się ona „aktywna”. - Domyślnie pobieranie strony nie przechodzi przez skrypt service worker, chyba że samo żądanie strony zostało przesłane przez skrypt service worker. Aby zobaczyć efekty działania usługi, musisz odświeżyć stronę.
clients.claim()
może zastąpić tę domyślną wartość i przejąć kontrolę nad stronami, które nie są kontrolowane.
Weź ten kod HTML:
<!DOCTYPE html>
An image will appear here in 3 seconds:
<script>
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('SW registered!', reg))
.catch(err => console.log('Boo!', err));
setTimeout(() => {
const img = new Image();
img.src = '/dog.svg';
document.body.appendChild(img);
}, 3000);
</script>
Rejestruje usługę w tle i po 3 sekundach dodaje obraz psa.
Oto jego usługa sw.js
:
self.addEventListener('install', event => {
console.log('V1 installing…');
// cache a cat SVG
event.waitUntil(
caches.open('static-v1').then(cache => cache.add('/cat.svg'))
);
});
self.addEventListener('activate', event => {
console.log('V1 now ready to handle fetches!');
});
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// serve the cat SVG from the cache if the request is
// same-origin and the path is '/dog.svg'
if (url.origin == location.origin && url.pathname == '/dog.svg') {
event.respondWith(caches.match('/cat.svg'));
}
});
Zapisują w pamięci podręcznej obraz kota i wyświetlają go, gdy tylko pojawi się żądanie /dog.svg
. Jeśli jednak uruchomisz powyższe przykładowe zapytanie, przy pierwszym załadowaniu strony zobaczysz psa. Kliknij odśwież, a zobaczysz kota.
Zakres i kontrola
Domyślny zakres rejestracji skryptu service worker to ./
w stosunku do adresu URL skryptu. Oznacza to, że jeśli zarejestrujesz skrypt service worker w adresie //example.com/foo/bar.js
, jego domyślny zakres to //example.com/foo/
.
Strony, pracowników i współużytkowanych pracowników nazywamy clients
. Twój serwis worker może kontrolować tylko klientów, którzy znajdują się w zakresie. Gdy klient jest „kontrolowany”, pobiera go za pomocą workera usługi w zakresie. Możesz wykryć, czy klient jest kontrolowany za pomocą navigator.serviceWorker.controller
, który będzie miał wartość null, czy instancji service workera.
Pobieranie, analizowanie i wykonywanie
Pierwszy pracownik usługi pobiera się, gdy wywołasz .register()
. Jeśli skrypt nie może zostać pobrany, przeanalizowany lub wywołany z błędem podczas początkowego wykonania, obietnica rejestracji zostanie odrzucona, a usługa workera zostanie odrzucona.
Narzędzia deweloperskie w Chrome wyświetlają błąd w konsoli oraz w sekcji service worker na karcie aplikacji:
Zainstaluj
Pierwszym zdarzeniem, które otrzymuje usługa, jest install
. Jest ona wywoływana, gdy tylko pracownik zostanie uruchomiony, i tylko raz na pracownika usługi. Jeśli zmienisz skrypt usługi, przeglądarka uzna go za inną usługę i przypisze mu własne zdarzenie install
. Aktualizacje omówię bardziej szczegółowo później.
Zdarzenie install
to Twoja szansa na zapisanie w pamięci podręcznej wszystkiego, czego potrzebujesz, zanim będziesz mógł kontrolować klientów. Obietnica przekazywana do event.waitUntil()
informuje przeglądarkę, kiedy instalacja została zakończona i czy zakończyła się pomyślnie.
Jeśli obietnica zostanie odrzucona, oznacza to, że instalacja się nie udała, a przeglądarka usuwa pracownika usługi. Nigdy nie będzie kontrolować klientów. Oznacza to, że możemy polegać na tym, że cat.svg
jest obecny w pamięci podręcznej w naszych zdarzeniach fetch
. Jest to zależność.
Aktywuj
Gdy Twój serwis worker będzie gotowy do kontrolowania klientów i obsługiwania zdarzeń funkcjonalnych, takich jak push
i sync
, otrzymasz zdarzenie activate
. Nie oznacza to jednak, że strona o nazwie .register()
będzie kontrolowana.
Gdy po raz pierwszy wczytujesz demo, mimo że usługa dog.svg
jest wywoływana długo po aktywacji pracownika usługi, nie obsługuje ona żądania i nadal widzisz obraz psa. Domyślnie jest to zgodność, czyli jeśli strona wczytuje się bez skryptu usługi, jej zasoby podrzędne też nie będą dostępne. Jeśli wczytasz demo po raz drugi (czyli odświeżasz stronę), będzie ono kontrolowane. Strona i obraz zostaną przetworzone przez zdarzenia fetch
, a zamiast nich zobaczysz kota.
clients.claim
Po aktywowaniu możesz przejąć kontrolę nad niezarządzanymi klientami, wywołując clients.claim()
w ramach swojego service workera.
Oto wariant powyższego przykładu, który wywołuje funkcję clients.claim()
w zdarzeniu activate
. Należy zobaczyć kota. Mówię „powinien”, ponieważ jest to kwestia czasu. Kota zobaczysz tylko wtedy, gdy usługa robocza zostanie uruchomiona i wprowadzi clients.claim()
w efekt przed próbą załadowania obrazu.
Jeśli używasz serwisu workera do wczytywania stron w inny sposób niż przez sieć, clients.claim()
może być kłopotliwy, ponieważ serwis workera będzie kontrolować niektórych klientów, którzy wczytali się bez niego.
Aktualizacja serwisu workera
W skrócie:
- Aktualizacja jest uruchamiana w jednym z tych przypadków:
- Nawigacja po stronie objętej zakresem.
- Zdarzenia funkcjonalne, takie jak
push
isync
, chyba że w ciągu ostatnich 24 godzin nastąpiła weryfikacja aktualizacji. - Wywoływanie
.register()
tylko wtedy, gdy adres URL usługi roboczej uległ zmianie. Należy jednak unikać zmiany adresu URL pracownika.
- Większość przeglądarek, w tym Chrome 68 i nowsze, domyślnie ignoruje nagłówki pamięci podręcznej podczas sprawdzania aktualizacji zarejestrowanego skryptu usługi. Nadal uwzględniają nagłówki pamięci podręcznej podczas pobierania zasobów załadowanych w ramach instancji roboczej za pomocą
importScripts()
. Możesz zmienić to domyślne zachowanie, ustawiając opcjęupdateViaCache
podczas rejestrowania pracownika usługi. - Twój serwis worker jest uważany za zaktualizowany, jeśli różni się od tego, który jest już w przeglądarce, o co najmniej 1 bajt. (rozszerzamy to na importowane skrypty i moduły).
- Zaktualizowany serwis worker jest uruchamiany obok dotychczasowego i otrzymuje własne zdarzenie
install
. - Jeśli nowy worker ma kod stanu inny niż OK (np. 404), nie udaje mu się przeanalizować danych, zgłasza błąd podczas wykonywania lub odrzuca instalację, nowy worker jest odrzucany, ale bieżący pozostaje aktywny.
- Po zainstalowaniu zaktualizowany proces roboczy będzie
wait
, dopóki istniejący proces roboczy nie będzie kontrolował żadnego klienta. (Pamiętaj, że podczas odświeżania klienci nakładają się na siebie). self.skipWaiting()
zapobiega oczekiwaniu, co oznacza, że usługa workera uruchamia się, gdy tylko zakończy się jej instalacja.
Załóżmy, że zmodyfikowaliśmy skrypt usługi, aby zamiast kota wyświetlał obraz konia:
const expectedCaches = ['static-v2'];
self.addEventListener('install', event => {
console.log('V2 installing…');
// cache a horse SVG into a new cache, static-v2
event.waitUntil(
caches.open('static-v2').then(cache => cache.add('/horse.svg'))
);
});
self.addEventListener('activate', event => {
// delete any caches that aren't in expectedCaches
// which will get rid of static-v1
event.waitUntil(
caches.keys().then(keys => Promise.all(
keys.map(key => {
if (!expectedCaches.includes(key)) {
return caches.delete(key);
}
})
)).then(() => {
console.log('V2 now ready to handle fetches!');
})
);
});
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// serve the horse SVG from the cache if the request is
// same-origin and the path is '/dog.svg'
if (url.origin == location.origin && url.pathname == '/dog.svg') {
event.respondWith(caches.match('/horse.svg'));
}
});
Obejrzyj prezentację powyższych funkcji Nadal powinieneś/powinnaś widzieć obraz kota. Oto dlaczego…
Zainstaluj
Zmieniłem nazwę pamięci podręcznej z static-v1
na static-v2
. Oznacza to, że mogę skonfigurować nową pamięć podręczną bez nadpisywania elementów w obecnej pamięci, z której nadal korzysta stary worker usługi.
Ten wzór tworzy pamięć podręczną dla poszczególnych wersji, podobnie jak zasoby, które aplikacja natywna łączy z plikiem wykonywalnym. Możesz też mieć pamięci podręczne, które nie są powiązane z konkretną wersją, np. avatars
.
Czekam
Po zainstalowaniu zaktualizowany serwis worker opóźnia aktywację do momentu, gdy istniejący serwis worker nie będzie już kontrolował klientów. Ten stan nazywa się „czekanie” i w ten sposób przeglądarka zapewnia, że jednocześnie działa tylko jedna wersja Twojego pracownika usługi.
Jeśli uruchomisz zaktualizowane demo, nadal zobaczysz zdjęcie kota, ponieważ pracownik V2 nie został jeszcze aktywowany. Nowy serwis worker znajdziesz na karcie „Aplikacja” w narzędziu DevTools:
Nawet jeśli masz otwartą tylko jedną kartę z wersją demonstracyjną, odświeżenie strony nie wystarczy, aby nowa wersja została załadowana. Wynika to z działania nawigacji w przeglądarce. Gdy się przemieszczasz, bieżąca strona nie zniknie, dopóki nie zostaną odebrane nagłówki odpowiedzi. Nawet wtedy może ona pozostać, jeśli odpowiedź zawiera nagłówek Content-Disposition
. Z powodu tego nakładania się skrypt service worker zawsze kontroluje klienta podczas odświeżania.
Aby uzyskać aktualizację, zamknij wszystkie karty lub zamknij usługę w tle. Gdy ponownie przejdziesz do wersji demonstracyjnej, powinieneś zobaczyć konia.
Ten schemat jest podobny do schematu aktualizacji Chrome. Aktualizacje Chrome są pobierane w tle, ale nie są stosowane, dopóki nie uruchomisz ponownie przeglądarki. W międzyczasie możesz nadal korzystać z obecnej wersji bez żadnych zakłóceń. Jest to jednak uciążliwe podczas tworzenia aplikacji, ale w narzędziach deweloperskich znajdziesz sposoby na ułatwienie tego procesu. Omówię je później w tym artykule.
Aktywuj
Ta funkcja jest wywoływana, gdy stary usługowiec przestanie działać, a nowy będzie mógł kontrolować klientów. To idealny moment na wykonanie zadań, których nie można było wykonać, gdy używano starego procesu roboczego, takich jak migracja baz danych i czyszczenie pamięci podręcznej.
W tym pokazie utrzymuję listę pamięci podręcznej, która powinna się tam znajdować, a w przypadku zdarzenia activate
pozbywam się wszystkich innych, co powoduje usunięcie starej pamięci podręcznej static-v1
.
Jeśli przekażesz obietnicę do funkcji event.waitUntil()
, będzie ona buforować zdarzenia funkcjonalne (fetch
, push
, sync
itp.) do czasu jej spełnienia. Gdy więc zdarzenie fetch
zostanie uruchomione, aktywacja zostanie w pełni zakończona.
Pomiń fazę oczekiwania
Faza oczekiwania oznacza, że w danym momencie działa tylko jedna wersja witryny, ale jeśli nie potrzebujesz tej funkcji, możesz wcześniej aktywować nowego skryptu service worker, wywołując funkcję self.skipWaiting()
.
Spowoduje to, że proces roboczy usługi wyrzuci bieżący aktywny proces roboczy i aktywuje się, gdy tylko wejdzie w fazę oczekiwania (lub natychmiast, jeśli jest już w tej fazie). Nie powoduje, że pracownik pomija instalację, tylko czeka.
Nie ma znaczenia, kiedy zadzwonisz do skipWaiting()
, o ile nastąpi to w trakcie oczekiwania lub przed nim. Zwykle jest to wywoływane w zdarzeniu install
:
self.addEventListener('install', event => {
self.skipWaiting();
event.waitUntil(
// caching etc
);
});
Możesz jednak wywołać tę funkcję jako wynik wywołania postMessage()
w ramach usługi. Oznacza to, że chcesz skipWaiting()
po interakcji z użytkownikiem.
Oto wersja demonstracyjna, która korzysta z elementu skipWaiting()
. Powinieneś zobaczyć obraz krowy bez konieczności przewijania. Podobnie jak w przypadku clients.claim()
, jest to wyścig, więc zobaczysz krowę tylko wtedy, gdy nowy skrypt service worker pobiera, instaluje i aktywuje się, zanim strona spróbuje wczytać obraz.
Aktualizacje ręczne
Jak już wspomniałem, przeglądarka automatycznie sprawdza dostępność aktualizacji po nawigacji i wydarzeniach funkcjonalnych, ale możesz też uruchamiać je ręcznie:
navigator.serviceWorker.register('/sw.js').then(reg => {
// sometime later…
reg.update();
});
Jeśli przewidujesz, że użytkownik będzie przez długi czas korzystać z Twojej witryny bez jej odświeżania, możesz wywoływać funkcję update()
w określonych odstępach czasu (np. co godzinę).
Unikaj zmiany adresu URL skryptu usługi.
Jeśli przeczytasz mój post na temat najlepszych praktyk dotyczących pamięci podręcznej, możesz rozważyć nadanie unikalnego adresu URL każdej wersji usługi dla workera. Nie rób tego! Jest to zwykle niewskazane w przypadku serwisów workerów. Wystarczy zaktualizować skrypt w jego bieżącej lokalizacji.
Może to spowodować taki problem:
index.html
rejestrujesw-v1.js
jako skrypt service worker.sw-v1.js
przechowuje w pamięci podręcznej i przekazujeindex.html
, więc działa głównie offline.- Uaktualniasz
index.html
, aby zarejestrował nowy, lśniącysw-v2.js
.
Jeśli wykonasz te czynności, użytkownik nigdy nie otrzyma sw-v2.js
, ponieważ sw-v1.js
wyświetla starą wersję index.html
z pamięci podręcznej. Musisz zaktualizować swojego pracownika usługi, aby zaktualizować pracownika usługi. Fuj.
W demo powyżej zmieniłem jednak adres URL usługi. W ramach tej funkcji możesz przełączać się między wersjami. Nie robię tego w wersji produkcyjnej.
Ułatwianie tworzenia
Cykl życia skryptu service worker jest zaprojektowany z uwzględnieniem użytkownika, ale podczas tworzenia może być trochę kłopotliwy. Na szczęście masz do dyspozycji kilka narzędzi, które Ci w tym pomogą:
Zaktualizuj przy ponownym załadowaniu
To jest mój ulubiony.
Zmiana ta ułatwia pracę programistom. Każda nawigacja:
- Ponowne pobieranie usługi roboczej.
- Zainstaluj ją jako nową wersję, nawet jeśli jest identyczna bit po bicie, co oznacza, że zdarzenie
install
zostanie uruchomione, a pamięci podręczne zostaną zaktualizowane. - Pomiń fazę oczekiwania, aby nowy serwis worker został aktywowany.
- Przejdź na stronę.
Oznacza to, że aktualizacje będą się pojawiać po każdej zmianie strony (w tym po odświeżeniu) bez konieczności ponownego wczytywania strony ani zamykania karty.
Pomiń oczekiwanie
Jeśli masz oczekującą instancję roboczą, możesz kliknąć „Pomiń oczekiwanie” w Narzędziach dla programistów, aby natychmiast przenieść ją do stanu „aktywna”.
Shift-reload
Jeśli wczytasz stronę przy użyciu przycisku Shift (czyli wczytujesz ją przymusowo), całkowicie pomijasz usługę. Nie będzie ona kontrolowana. Ta funkcja jest opisana w specyfikacji, więc działa w innych przeglądarkach obsługujących workery usługi.
Obsługa aktualizacji
Usługa workera została zaprojektowana jako część rozszerzalnego internetu. Chodzi o to, że my, jako twórcy przeglądarek, zdajemy sobie sprawę, że nie jesteśmy lepsi w programowaniu stron internetowych niż web developerzy. Dlatego nie udostępniamy wąskich interfejsów API na wysokim poziomie, które rozwiązują konkretny problem za pomocą wzorów, które nam odpowiadają. Zamiast tego dajemy Ci dostęp do rdzenia przeglądarki i pozwalamy na działanie według uznania, w sposób, który najlepiej sprawdza się w przypadku Twoich użytkowników.
Aby umożliwić jak najwięcej wzorców, cały cykl aktualizacji jest widoczny:
navigator.serviceWorker.register('/sw.js').then(reg => {
reg.installing; // the installing worker, or undefined
reg.waiting; // the waiting worker, or undefined
reg.active; // the active worker, or undefined
reg.addEventListener('updatefound', () => {
// A wild service worker has appeared in reg.installing!
const newWorker = reg.installing;
newWorker.state;
// "installing" - the install event has fired, but not yet complete
// "installed" - install complete
// "activating" - the activate event has fired, but not yet complete
// "activated" - fully active
// "redundant" - discarded. Either failed install, or it's been
// replaced by a newer version
newWorker.addEventListener('statechange', () => {
// newWorker.state has changed
});
});
});
navigator.serviceWorker.addEventListener('controllerchange', () => {
// This fires when the service worker controlling this page
// changes, eg a new worker has skipped waiting and become
// the new active worker.
});
Cykl życia trwa dalej
Jak widzisz, warto poznać cykl życia usługi. Dzięki temu działanie usługi powinno wydawać się bardziej logiczne i mniej tajemnicze. Dzięki temu zyskasz pewność, że wdrażasz i aktualizujesz usługi w sposób prawidłowy.