Wie Sie über Dienstprogramme nachdenken sollten.
Dienstprogramme sind leistungsstark und es lohnt sich, sie zu lernen. Sie ermöglichen es Ihnen, Ihren Nutzern eine völlig neue Erfahrung zu bieten. Ihre Website kann sofort geladen werden. Sie kann offline verwendet werden. Sie kann als plattformspezifische App installiert werden und ist genauso ausgefeilt wie eine native App – aber mit der Reichweite und Freiheit des Webs.
Service Worker sind jedoch anders als das, was die meisten Webentwickler gewohnt sind. Sie haben eine steile Lernkurve und einige Stolpersteine, auf die Sie achten müssen.
Ich habe vor Kurzem mit Google-Entwicklern an einem Projekt gearbeitet: Service Workies, ein kostenloses Spiel zum Verständnis von Dienstprogrammen. Beim Erstellen und Arbeiten mit den komplexen Details von Service Workers stieß ich auf einige Probleme. Am meisten hat mir geholfen, ein paar beschreibende Metaphern zu entwickeln. In diesem Beitrag werden wir uns mit diesen mentalen Modellen befassen und uns mit den paradoxen Eigenschaften auseinandersetzen, die Servicemitarbeiter sowohl schwierig als auch großartig machen.
Dasselbe, aber anders
Beim Codieren Ihres Dienstarbeiters werden Ihnen viele Dinge bekannt vorkommen. Sie können Ihre bevorzugten neuen JavaScript-Sprachfunktionen verwenden. Lifecycle-Ereignisse werden genauso wie UI-Ereignisse überwacht. Sie steuern die Ablaufsteuerung wie gewohnt mit Promises.
Aber bei anderen Service Worker-Verhaltensweisen können Sie nur den Kopf schütteln. Das ist besonders dann der Fall, wenn Sie die Seite aktualisieren und Ihre Codeänderungen nicht angewendet werden.
Eine neue Ebene
Normalerweise müssen Sie beim Erstellen einer Website nur zwei Ebenen berücksichtigen: den Client und den Server. Der Service Worker ist eine brandneue Schicht, die sich in der Mitte befindet.
Stellen Sie sich Ihren Service Worker als eine Art Browsererweiterung vor, die Ihre Website im Browser des Nutzers installieren kann. Nach der Installation erweitert der Dienst-Worker den Browser für Ihre Website um eine leistungsstarke Zwischenschicht. Diese Service Worker-Ebene kann alle Anfragen Ihrer Website abfangen und verarbeiten.
Die Dienstworker-Ebene hat einen eigenen Lebenszyklus, der unabhängig vom Browsertab ist. Ein einfacher Seitenaktualisierungsvorgang reicht nicht aus, um einen Service Worker zu aktualisieren. Genauso wenig würden Sie erwarten, dass durch eine Seitenaktualisierung Code aktualisiert wird, der auf einem Server bereitgestellt wird. Für jede Ebene gelten eigene Aktualisierungsregeln.
Im Spiel Service Workies erfahren Sie alles über den Lebenszyklus von Dienstprogrammen und können viel Praxiserfahrung sammeln.
Leistungsstark, aber eingeschränkt
Ein Service Worker auf Ihrer Website bietet Ihnen viele Vorteile. Ihre Website kann:
- auch dann einwandfrei funktionieren, wenn der Nutzer offline ist
- Caching für enorme Leistungssteigerungen
- Push-Benachrichtigungen verwenden
- als PWA installiert werden.
Service Worker sind zwar sehr leistungsfähig, aber von Natur aus eingeschränkt. Sie können nichts synchron oder im selben Thread wie Ihre Website tun. Das bedeutet, dass Sie keinen Zugriff auf Folgendes haben:
- localStorage
- DOM
- das Fenster
Es gibt mehrere Möglichkeiten, wie Ihre Seite mit dem Service Worker kommunizieren kann, darunter direkte postMessage
, Nachrichtenkanäle und Broadcast-Kanäle.
Langlebige, aber kurzlebige
Ein aktiver Service Worker bleibt auch dann aktiv, wenn ein Nutzer Ihre Website verlässt oder den Tab schließt. Der Browser hält diesen Service Worker bei, damit er bereit ist, wenn der Nutzer das nächste Mal Ihre Website besucht. Bevor die erste Anfrage gestellt wird, hat der Service Worker die Möglichkeit, sie abzufangen und die Kontrolle über die Seite zu übernehmen. So kann eine Website auch offline funktionieren: Der Service Worker kann eine im Cache gespeicherte Version der Seite selbst bereitstellen, auch wenn der Nutzer keine Internetverbindung hat.
In Service Workies wird dieses Konzept mit Kolohe (einem freundlichen Dienstarbeiter) veranschaulicht, der Anfragen abfängt und verarbeitet.
Angehalten
Auch wenn Service Worker unsterblich erscheinen, können sie fast jederzeit angehalten werden. Der Browser möchte keine Ressourcen für einen Service Worker verschwenden, der derzeit nichts tut. Ein Stopp ist nicht dasselbe wie eine Beendigung – der Dienst-Worker bleibt installiert und aktiviert. Er wird nur in den Ruhemodus versetzt. Wenn sie das nächste Mal benötigt wird (z.B. zum Ausführen einer Anfrage), wird sie vom Browser wieder aktiviert.
waitUntil
Da der Dienst im Hintergrund jederzeit in den Ruhemodus versetzt werden kann, muss er dem Browser mitteilen können, wenn er gerade etwas Wichtiges tut und nicht in den Ruhemodus versetzt werden möchte. Hier kommt event.waitUntil()
ins Spiel. Mit dieser Methode wird der Lebenszyklus verlängert, sodass er nicht beendet und nicht zur nächsten Phase übergegangen wird, bis wir bereit sind. So haben wir Zeit, Caches einzurichten, Ressourcen aus dem Netzwerk abzurufen usw.
In diesem Beispiel wird dem Browser mitgeteilt, dass die Installation des Service Workers erst abgeschlossen ist, wenn der assets
-Cache erstellt und mit dem Bild eines Schwertes gefüllt wurde:
self.addEventListener("install", event => {
event.waitUntil(
caches.open("assets").then(cache => {
return cache.addAll(["/weapons/sword/blade.png"]);
})
);
});
Achten Sie auf den globalen Zustand
Bei diesem Start/Stopp wird der globale Gültigkeitsbereich des Dienstarbeiters zurückgesetzt. Verwenden Sie also keinen globalen Status in Ihrem Service Worker, da er sonst beim nächsten Starten einen anderen Status als erwartet haben könnte.
Betrachten Sie dieses Beispiel mit einem globalen Status:
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
Bei jeder Anfrage protokolliert dieser Service Worker eine Zahl, z. B. 0.13981866382421893
. Die Variable hasHandledARequest
ändert sich ebenfalls in true
. Der Dienst-Worker ist jetzt eine Weile inaktiv, sodass der Browser ihn beendet. Wenn das nächste Mal eine Anfrage erfolgt, wird der Service Worker wieder benötigt und der Browser weckt ihn auf. Das Script wird erneut ausgewertet. Jetzt ist hasHandledARequest
auf false
zurückgesetzt und favoriteNumber
ist etwas ganz anderes: 0.5907281835659033
.
Sie können sich nicht auf den gespeicherten Status in einem Service Worker verlassen. Außerdem kann das Erstellen von Instanzen von Elementen wie Nachrichtenkanälen zu Fehlern führen: Jedes Mal, wenn der Dienstarbeiter angehalten oder gestartet wird, wird eine brandneue Instanz erstellt.
In Kapitel 3 wird unser angehaltener Dienstarbeiter als farblos dargestellt, während er darauf wartet, geweckt zu werden.
Zusammen, aber getrennt
Ihre Seite kann immer nur von einem Service Worker gesteuert werden. Es können jedoch zwei Service Worker gleichzeitig installiert werden. Wenn Sie eine Änderung am Service Worker-Code vornehmen und die Seite aktualisieren, bearbeiten Sie den Service Worker nicht. Dienstprogramme sind immutable. Sie erstellen stattdessen eine ganz neue. Dieser neue Dienst-Worker (nennen wir ihn SW2) wird installiert, aber noch nicht aktiviert. Er muss warten, bis der aktuelle Service Worker (SW1) beendet wird (wenn der Nutzer Ihre Website verlässt).
Caches anderer Service Worker manipulieren
Während der Installation kann SW2 die Einrichtung übernehmen, in der Regel durch Erstellen und Befüllen von Caches. Hinweis: Dieser neue Dienst-Worker hat Zugriff auf alles, auf das der aktuelle Dienst-Worker Zugriff hat. Wenn Sie nicht vorsichtig sind, kann der neue wartende Service Worker Ihrem aktuellen Service Worker das Leben schwer machen. Beispiele für Probleme:
- SW2 könnte einen Cache löschen, der von SW1 aktiv verwendet wird.
- SW2 könnte den Inhalt eines Caches bearbeiten, den SW1 verwendet, wodurch SW1 mit Assets antwortet, die für die Seite nicht vorgesehen sind.
Skip skipWaiting
Ein Service Worker kann auch die riskante Methode skipWaiting()
verwenden, um die Kontrolle über die Seite zu übernehmen, sobald die Installation abgeschlossen ist. Das ist in der Regel keine gute Idee, es sei denn, Sie möchten einen fehlerhaften Service Worker ersetzen. Der neue Dienst-Worker verwendet möglicherweise aktualisierte Ressourcen, die von der aktuellen Seite nicht erwartet werden, was zu Fehlern führt.
Mit einem sauberen System beginnen
Sie können verhindern, dass Ihre Service Worker sich gegenseitig überschreiben, indem Sie dafür sorgen, dass sie unterschiedliche Caches verwenden. Am einfachsten geht das, indem Sie die verwendeten Cache-Namen versionieren.
const version = 1;
const assetCacheName = `assets-${version}`;
self.addEventListener("install", event => {
caches.open(assetCacheName).then(cache => {
// confidently do stuff with your very own cache
});
});
Wenn Sie einen neuen Service Worker bereitstellen, erhöhen Sie die version
, damit er mit einem völlig separaten Cache wie vorgesehen funktioniert.
Endreinigung
Sobald Ihr Dienstarbeiter den Status activated
erreicht, wissen Sie, dass er die Kontrolle übernommen hat und der vorherige Dienstarbeiter redundant ist. An dieser Stelle ist es wichtig, den alten Service Worker zu bereinigen. So werden nicht nur die Cache-Speicherlimits Ihrer Nutzer eingehalten, sondern auch unbeabsichtigte Fehler vermieden.
Die Methode caches.match()
ist ein häufig verwendeter Tastenkürzel zum Abrufen eines Elements aus einem beliebigen Cache, in dem eine Übereinstimmung gefunden wird. Die Caches werden jedoch in der Reihenfolge durchgegangen, in der sie erstellt wurden. Angenommen, Sie haben zwei Versionen einer Scriptdatei app.js
in zwei verschiedenen Caches: assets-1
und assets-2
. Auf Ihrer Seite wird das neuere Script erwartet, das in assets-2
gespeichert ist. Wenn Sie den alten Cache jedoch nicht gelöscht haben, gibt caches.match('app.js')
den alten Cache von assets-1
zurück und Ihre Website funktioniert höchstwahrscheinlich nicht mehr.
Zum Bereinigen der vorherigen Service Worker müssen Sie nur den Cache löschen, den der neue Service Worker nicht benötigt:
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);
}
});
);
});
);
});
Es erfordert etwas Arbeit und Disziplin, zu verhindern, dass sich Ihre Service Worker gegenseitig blockieren. Der Aufwand lohnt sich aber.
Service Worker-Mindset
Wenn Sie die richtige Einstellung zu Service Workern haben, können Sie sie mit Zuversicht erstellen. Sobald Sie die Funktion beherrschen, können Sie Ihren Nutzern eine hervorragende Nutzererfahrung bieten.
Wenn Sie all das durch Spielen verstehen möchten, haben Sie Glück! Spielen Sie Service Workies, um die Geheimnisse von Service Workern zu erfahren und die Offline-Monster zu besiegen.