Der Lebenszyklus des Service Workers ist der komplizierteste Teil. Wenn Sie nicht wissen, was das System versucht und welche Vorteile es bietet, kann es sich so anfühlen, als würde es gegen Sie arbeiten. Wenn Sie jedoch wissen, wie es funktioniert, können Sie Nutzern nahtlose, unaufdringliche Updates bereitstellen und dabei die besten Web- und nativen Muster kombinieren.
Dies ist eine detaillierte Übersicht, aber die Aufzählungspunkte am Anfang jedes Abschnitts decken das meiste ab, was Sie wissen müssen.
Die Absicht
Der Lebenszyklus dient folgenden Zwecken:
- Offline-First-Ansatz ermöglichen
- Ermöglicht es einem neuen Service Worker, sich vorzubereiten, ohne den aktuellen zu unterbrechen.
- Achten Sie darauf, dass eine Seite im Geltungsbereich durchgehend von demselben Service Worker (oder von keinem Service Worker) gesteuert wird.
- Achten Sie darauf, dass immer nur eine Version Ihrer Website ausgeführt wird.
Der letzte Punkt ist ziemlich wichtig. Ohne Service Worker können Nutzer einen Tab auf Ihrer Website laden und später einen anderen öffnen. Dies kann dazu führen, dass zwei Versionen Ihrer Website gleichzeitig ausgeführt werden. Manchmal ist das in Ordnung, aber wenn es um Speicher geht, kann es leicht passieren, dass zwei Tabs sehr unterschiedliche Ansichten darüber haben, wie ihr freigegebener Speicher verwaltet werden soll. Dies kann zu Fehlern oder sogar zu Datenverlust führen.
Der erste Service Worker
Kurz gesagt bedeutet das:
- Das
install-Ereignis ist das erste Ereignis, das ein Service Worker erhält, und es tritt nur einmal auf. - Ein an
installEvent.waitUntil()übergebenes Promise signalisiert die Dauer und den Erfolg oder Misserfolg der Installation. - Ein Service Worker empfängt Ereignisse wie
fetchundpusherst, wenn er erfolgreich installiert wurde und „aktiv“ ist. - Standardmäßig werden die Abrufe einer Seite nicht über einen Service Worker geleitet, es sei denn, die Seitenanfrage selbst wurde über einen Service Worker geleitet. Sie müssen die Seite also aktualisieren, um die Auswirkungen des Service Workers zu sehen.
clients.claim()kann diese Standardeinstellung überschreiben und die Kontrolle über nicht kontrollierte Seiten übernehmen.
Beispiel-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>
Es registriert einen Service Worker und fügt nach 3 Sekunden ein Bild eines Hundes hinzu.
Hier ist der Service Worker 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'));
}
});
Es speichert ein Bild einer Katze im Cache und stellt es bereit, wenn eine Anfrage für /dog.svg eingeht. Wenn Sie jedoch das obige Beispiel ausführen, wird beim ersten Laden der Seite ein Hund angezeigt. Aktualisieren Sie die Seite, um die Katze zu sehen.
Umfang und Kontrolle
Der Standardbereich einer Service Worker-Registrierung ist ./ relativ zur Skript-URL. Wenn Sie also einen Service Worker unter //example.com/foo/bar.js registrieren, hat er den Standardbereich //example.com/foo/.
Seiten, Worker und freigegebene Worker werden als clients bezeichnet. Ihr Service Worker kann nur Clients steuern, die sich im Bereich befinden. Sobald ein Client „kontrolliert“ wird, werden seine Abrufe über den Service Worker im Bereich ausgeführt. Sie können erkennen, ob ein Client über navigator.serviceWorker.controller gesteuert wird. Der Wert ist dann entweder null oder eine Service Worker-Instanz.
Herunterladen, parsen und ausführen
Der allererste Service Worker wird heruntergeladen, wenn Sie .register() aufrufen. Wenn das Skript nicht heruntergeladen oder geparst werden kann oder bei der ersten Ausführung ein Fehler auftritt, wird das Register-Promise abgelehnt und der Service Worker wird verworfen.
In den Chrome-Entwicklertools wird der Fehler in der Konsole und im Abschnitt „Service Worker“ des Tabs „Anwendung“ angezeigt:
Installieren
Das erste Ereignis, das ein Service Worker erhält, ist install. Es wird ausgelöst, sobald der Worker ausgeführt wird, und nur einmal pro Service Worker aufgerufen. Wenn Sie Ihr Service Worker-Script ändern, wird es vom Browser als anderer Service Worker betrachtet und erhält ein eigenes install-Ereignis. Später gehe ich noch genauer auf Updates ein.
Das Ereignis install ist Ihre Chance, alles zu cachen, was Sie benötigen, bevor Sie Clients steuern können. Das Promise, das Sie an event.waitUntil() übergeben, informiert den Browser darüber, wann die Installation abgeschlossen ist und ob sie erfolgreich war.
Wenn das Promise abgelehnt wird, bedeutet das, dass die Installation fehlgeschlagen ist, und der Browser verwirft den Service Worker. Sie steuert niemals Clients. Das bedeutet, dass wir uns darauf verlassen können, dass cat.svg in den fetch-Ereignissen im Cache vorhanden ist. Es ist eine Abhängigkeit.
Aktivieren
Sobald Ihr Service Worker bereit ist, Clients zu steuern und funktionale Ereignisse wie push und sync zu verarbeiten, erhalten Sie ein activate-Ereignis. Das bedeutet jedoch nicht, dass die Seite, die .register() aufgerufen hat, kontrolliert wird.
Wenn Sie die Demo zum ersten Mal laden, wird die Anfrage für dog.svg nicht verarbeitet, obwohl sie lange nach der Aktivierung des Service Workers gestellt wird. Sie sehen also weiterhin das Bild des Hundes. Die Standardeinstellung ist Konsistenz. Wenn Ihre Seite ohne Service Worker geladen wird, werden auch die Unterressourcen nicht geladen. Wenn Sie die Demo ein zweites Mal laden (also die Seite aktualisieren), wird sie gesteuert. Sowohl die Seite als auch das Bild durchlaufen fetch-Ereignisse und Sie sehen stattdessen eine Katze.
clients.claim
Sie können die Kontrolle über unkontrollierte Clients übernehmen, indem Sie clients.claim() in Ihrem Service Worker aufrufen, sobald er aktiviert ist.
Hier ist eine Variante der obigen Demo, bei der clients.claim() im activate-Ereignis aufgerufen wird. Beim ersten Mal sollte eine Katze zu sehen sein. Ich sage „sollte“, weil das Timing hier entscheidend ist. Sie sehen nur dann eine Katze, wenn der Service Worker aktiviert wird und clients.claim() wirksam wird, bevor das Bild geladen wird.
Wenn Sie Ihren Service Worker verwenden, um Seiten anders zu laden, als sie über das Netzwerk geladen würden, kann clients.claim() problematisch sein, da Ihr Service Worker am Ende einige Clients steuert, die ohne ihn geladen wurden.
Service Worker aktualisieren
Kurz gesagt bedeutet das:
- In folgenden Fällen wird ein Update ausgelöst:
- Eine Navigation zu einer Seite im Geltungsbereich.
- Ein funktionales Ereignis wie
pushundsync, es sei denn, in den letzten 24 Stunden wurde eine Updateprüfung durchgeführt. .register()wird nur dann aufgerufen, wenn sich die Service Worker-URL geändert hat. Sie sollten jedoch die Worker-URL nicht ändern.
- Die meisten Browser, einschließlich Chrome 68 und höher, ignorieren standardmäßig Caching-Header, wenn sie nach Updates des registrierten Service Worker-Skripts suchen. Caching-Header werden weiterhin berücksichtigt, wenn Ressourcen abgerufen werden, die in einem Service Worker über
importScripts()geladen werden. Sie können dieses Standardverhalten überschreiben, indem Sie beim Registrieren des Service Workers die OptionupdateViaCachefestlegen. - Ihr Service Worker gilt als aktualisiert, wenn er sich byte-weise von dem unterscheidet, den der Browser bereits hat. (Wir erweitern dies auch auf importierte Skripts/Module.)
- Der aktualisierte Service Worker wird zusammen mit dem vorhandenen gestartet und erhält ein eigenes
install-Ereignis. - Wenn der neue Worker einen Statuscode ungleich „OK“ hat (z. B. 404), nicht geparst werden kann, während der Ausführung einen Fehler ausgibt oder während der Installation abgelehnt wird, wird er verworfen, der aktuelle Worker bleibt jedoch aktiv.
- Nach der erfolgreichen Installation wird der aktualisierte Worker
wait, bis der vorhandene Worker keine Clients mehr steuert. (Beachten Sie, dass sich Clients während einer Aktualisierung überschneiden.) self.skipWaiting()verhindert das Warten, d. h., der Service Worker wird aktiviert, sobald er installiert ist.
Angenommen, wir haben unser Service Worker-Skript so geändert, dass es mit einem Bild eines Pferdes statt einer Katze antwortet:
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'));
}
});
Demo ansehen Sie sollten immer noch ein Bild einer Katze sehen. Das hat folgenden Grund…
Installieren
Beachten Sie, dass ich den Cache-Namen von static-v1 in static-v2 geändert habe. So kann ich den neuen Cache einrichten, ohne Elemente im aktuellen Cache zu überschreiben, der noch vom alten Service Worker verwendet wird.
Mit diesem Muster werden versionsspezifische Caches erstellt, ähnlich wie Assets, die eine systemeigene App mit ihrer ausführbaren Datei bündeln würde. Möglicherweise haben Sie auch Caches, die nicht versionsspezifisch sind, z. B. avatars.
Warten
Nach der erfolgreichen Installation wird der aktualisierte Service Worker erst aktiviert, wenn der vorhandene Service Worker keine Clients mehr steuert. Dieser Status wird als „Warten“ bezeichnet. So sorgt der Browser dafür, dass jeweils nur eine Version Ihres Service Workers ausgeführt wird.
Wenn Sie die aktualisierte Demo ausgeführt haben, sollten Sie immer noch ein Bild einer Katze sehen, da der V2-Worker noch nicht aktiviert wurde. Der neue Service Worker wird auf dem Tab „Application“ der Entwicklertools angezeigt:
Selbst wenn Sie nur einen Tab mit der Demo geöffnet haben, reicht es nicht aus, die Seite zu aktualisieren, damit die neue Version übernommen wird. Das liegt daran, wie die Navigation im Browser funktioniert. Wenn Sie eine andere Seite aufrufen, wird die aktuelle Seite erst ausgeblendet, wenn die Antwortheader empfangen wurden. Selbst dann kann die aktuelle Seite noch angezeigt werden, wenn die Antwort einen Content-Disposition-Header enthält. Aufgrund dieser Überschneidung steuert der aktuelle Service Worker während einer Aktualisierung immer einen Client.
Um das Update zu erhalten, schließen Sie alle Tabs, die den aktuellen Service Worker verwenden, oder verlassen Sie sie. Wenn Sie dann noch einmal zur Demo wechseln, sollte das Pferd zu sehen sein.
Dieses Muster ähnelt dem, wie Chrome aktualisiert wird. Updates für Chrome werden im Hintergrund heruntergeladen, aber erst beim Neustart von Chrome angewendet. In der Zwischenzeit können Sie die aktuelle Version ohne Unterbrechung weiter verwenden. Das ist während der Entwicklung jedoch mühsam. Die Entwicklertools bieten jedoch Möglichkeiten, dies zu vereinfachen, auf die ich später in diesem Artikel eingehen werde.
Aktivieren
Dieses Ereignis wird ausgelöst, sobald der alte Service Worker nicht mehr vorhanden ist und der neue Service Worker Clients steuern kann. Dies ist der ideale Zeitpunkt, um Aufgaben zu erledigen, die Sie nicht ausführen konnten, während der alte Worker noch verwendet wurde, z. B. Datenbanken migrieren und Caches leeren.
In der Demo oben führe ich eine Liste der Caches, die ich erwarte, und im activate-Ereignis entferne ich alle anderen, wodurch der alte static-v1-Cache entfernt wird.
Wenn Sie ein Promise an event.waitUntil() übergeben, werden funktionale Ereignisse (fetch, push, sync usw.) gepuffert, bis das Promise aufgelöst wird. Wenn das Ereignis fetch ausgelöst wird, ist die Aktivierung also vollständig abgeschlossen.
Wartephase überspringen
Die Wartephase bedeutet, dass immer nur eine Version Ihrer Website ausgeführt wird. Wenn Sie diese Funktion nicht benötigen, können Sie den neuen Service Worker früher aktivieren, indem Sie self.skipWaiting() aufrufen.
Dadurch wird der aktuelle aktive Worker entfernt und der neue Worker aktiviert, sobald er in die Wartephase eintritt (oder sofort, wenn er sich bereits in der Wartephase befindet). Der Worker überspringt die Installation nicht, sondern wartet nur.
Es spielt keine Rolle, wann Sie skipWaiting() aufrufen, solange es während oder vor dem Warten geschieht. Es ist üblich, sie im install-Ereignis aufzurufen:
self.addEventListener('install', event => {
self.skipWaiting();
event.waitUntil(
// caching etc
);
});
Sie können sie aber auch als Ergebnis eines postMessage()-Aufrufs an den Service Worker aufrufen. Sie möchten also nach einer Nutzerinteraktion skipWaiting().
Hier ist eine Demo, in der skipWaiting() verwendet wird. Sie sollten ein Bild einer Kuh sehen, ohne die Seite verlassen zu müssen. Wie bei clients.claim() ist es ein Wettlauf. Sie sehen die Kuh also nur, wenn der neue Service Worker abgerufen, installiert und aktiviert wird, bevor die Seite versucht, das Bild zu laden.
Manuelle Updates
Wie bereits erwähnt, sucht der Browser nach Navigationen und funktionalen Ereignissen automatisch nach Updates. Sie können die Suche aber auch manuell auslösen:
navigator.serviceWorker.register('/sw.js').then(reg => {
// sometime later…
reg.update();
});
Wenn Sie davon ausgehen, dass der Nutzer Ihre Website lange Zeit verwendet, ohne sie neu zu laden, sollten Sie update() in einem bestimmten Intervall (z. B. stündlich) aufrufen.
Vermeiden Sie es, die URL Ihres Service Worker-Skripts zu ändern.
Wenn Sie meinen Beitrag zu Best Practices für das Caching gelesen haben, sollten Sie jeder Version Ihres Service Workers eine eindeutige URL zuweisen. Das sollten Sie nicht tun. Dies ist für Service Worker in der Regel keine gute Praxis. Aktualisieren Sie das Skript einfach an seinem aktuellen Speicherort.
Das kann zu einem Problem wie diesem führen:
index.htmlregistriertsw-v1.jsals Service Worker.sw-v1.jsspeichertindex.htmlim Cache und stellt es bereit, sodass es offline funktioniert.- Du aktualisierst
index.html, damit dein neuessw-v2.jsregistriert wird.
Wenn Sie die oben genannten Schritte ausführen, erhält der Nutzer nie sw-v2.js, da sw-v1.js die alte Version von index.html aus dem Cache bereitstellt. Sie haben sich in eine Situation gebracht, in der Sie Ihren Service Worker aktualisieren müssen, um Ihren Service Worker zu aktualisieren. Urgh.
Für die Demo oben habe ich die URL des Service Workers jedoch geändert. So können Sie für die Demo zwischen den Versionen wechseln. In einer Produktionsumgebung würde ich das nicht tun.
Entwicklung leicht gemacht
Der Lebenszyklus von Service Workern ist auf die Nutzer ausgerichtet, aber während der Entwicklung ist er etwas mühsam. Zum Glück gibt es einige Tools, die Ihnen dabei helfen können:
Beim Aktualisieren aktualisieren
Das ist mein Lieblingsbild.
Dadurch wird der Lebenszyklus entwicklerfreundlicher. Bei jeder Navigation wird Folgendes ausgeführt:
- Service Worker noch einmal abrufen.
- Installieren Sie sie als neue Version, auch wenn sie byteidentisch ist. Das bedeutet, dass Ihr
install-Ereignis ausgeführt und Ihre Caches aktualisiert werden. - Überspringen Sie die Wartephase, damit der neue Service Worker aktiviert wird.
- Auf der Seite navigieren.
Das bedeutet, dass Sie bei jeder Navigation (einschließlich Aktualisierung) die neuesten Daten erhalten, ohne die Seite zweimal neu laden oder den Tab schließen zu müssen.
Warten überspringen
Wenn ein Worker wartet, können Sie in den Entwicklertools auf „Warten überspringen“ klicken, um ihn sofort auf „Aktiv“ zu setzen.
Umschalttaste + Aktualisieren
Wenn Sie die Seite mit der Tastenkombination „Umschalttaste + Aktualisieren“ neu laden, wird der Service Worker vollständig umgangen. Es wird unkontrolliert sein. Diese Funktion ist in der Spezifikation enthalten und funktioniert daher auch in anderen Browsern, die Service Worker unterstützen.
Umgang mit Updates
Der Service Worker wurde als Teil des erweiterbaren Webs entwickelt. Die Idee dahinter ist, dass wir als Browserentwickler anerkennen, dass wir nicht besser in der Webentwicklung sind als Webentwickler. Daher sollten wir keine eingeschränkten High-Level-APIs bereitstellen, die ein bestimmtes Problem mit Mustern lösen, die uns gefallen. Stattdessen sollten wir Ihnen Zugriff auf die internen Funktionen des Browsers geben und Sie die Dinge so erledigen lassen, wie Sie es möchten, und zwar so, wie es für Ihre Nutzer am besten funktioniert.
Damit so viele Muster wie möglich erkannt werden können, ist der gesamte Aktualisierungszyklus beobachtbar:
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.
});
Der Lebenszyklus geht immer weiter
Wie Sie sehen, lohnt es sich, den Lebenszyklus von Service Workern zu verstehen. Mit diesem Wissen sollte das Verhalten von Service Workern logischer und weniger mysteriös erscheinen. Dieses Wissen gibt Ihnen mehr Sicherheit, wenn Sie Service Worker bereitstellen und aktualisieren.