Servicearbetare är kraftfulla och absolut värda att lära sig. De låter dig leverera en helt ny nivå av upplevelser till dina användare. Din webbplats kan laddas omedelbart. Den kan fungera offline. Den kan installeras som en inhemsk app och kännas lika snygg – men med webbens räckvidd och frihet.
Men service workers är inte likt något som de flesta av oss webbutvecklare är vana vid. De kommer med en brant inlärningskurva och en handfull fallgropar som du måste se upp för.
Google Developers och jag samarbetade nyligen på ett projekt-Service Workies – ett gratisspel för att förstå servicearbetare. När jag byggde det och arbetade med de komplexa detaljerna kring servicearbetare stötte jag på några problem. Det som hjälpte mig mest var att komma på en handfull beskrivande metaforer. I det här inlägget ska vi utforska dessa mentala modeller och sätta oss in i de paradoxala egenskaper som gör servicearbetare både knepiga och fantastiska.
Samma, men olika #
När du kodar din servicearbetare kommer många saker att kännas bekanta. Du får använda dina nya favoritfunktioner i JavaScript-språket. Du lyssnar på livscykelhändelser precis som med UI-händelser. Du hanterar kontrollflödet med löften som du är van vid.
Men andra beteenden hos service workers får dig att klia dig i huvudet i förvirring. Särskilt när du uppdaterar sidan och inte ser att dina kodändringar tillämpas.
Ett nytt lager #
Normalt när du bygger en webbplats har du bara två lager att tänka på: klienten och servern. Tjänstearbetaren är ett helt nytt lager som ligger i mitten.
Tänk på din tjänstearbetare som ett slags webbläsartillägg – ett som din webbplats kan installera i användarens webbläsare. När den väl är installerad utökar servicearbetaren webbläsaren för din webbplats med ett kraftfullt mellanskikt. Detta lager kan fånga upp och hantera alla förfrågningar som din webbplats gör.
Servicemedarbetarskiktet har en egen livscykel som är oberoende av webbläsarfliken. En enkel uppdatering av sidan räcker inte för att uppdatera en servicearbetare – precis som du inte förväntar dig att en uppdatering av sidan ska uppdatera kod som finns på en server. Varje lager har sina egna unika regler för uppdatering.
I spelet Service Workies täcker vi de många detaljerna i Service Workers livscykel och ger dig massor av övning i att arbeta med det.
Tänk på din Service Worker som ett nytt mellanskikt med en egen livscykel och egna metoder för uppdatering.
Mäktigt, men begränsat #
Att ha en Service Worker på din webbplats ger dig otroliga fördelar. Din webbplats kan:
- arbeta felfritt även när användaren är offline
- få massiva prestandaförbättringar genom caching
- använda push-notiser
- installeras som en PWA
Och hur mycket som helst som tjänstearbetarna kan göra är de begränsade av designen. De kan inte göra något synkront eller i samma tråd som din webbplats. Det innebär alltså ingen tillgång till:
- localStorage
- the DOM
- the window
Den goda nyheten är att det finns en handfull sätt som din sida kan kommunicera med sin serviceworker på, bland annat genom direkt postMessage
, en-till-ett-meddelande-kanaler och en-till-många Broadcast-kanaler.
Tänk på din serviceworker som något som lever utanför din sida. Du kan prata med den, men den kan inte komma åt din sida direkt.
Långlivad, men kortlivad #
En aktiv tjänstemedarbetare fortsätter att leva även efter att en användare lämnar din webbplats eller stänger fliken. Webbläsaren behåller den här tjänstearbetaren så att den är redo nästa gång användaren kommer tillbaka till din webbplats. Innan den allra första begäran görs får servicearbetaren en chans att fånga upp den och ta kontroll över sidan. Det är detta som gör att en webbplats kan fungera offline – servicearbetaren kan servera en cachad version av själva sidan, även om användaren inte har någon anslutning till internet.
I Service Workies visualiserar vi detta koncept med Kolohe (en vänlig servicearbetare) som avlyssnar och hanterar förfrågningar.
Stoppad #
Trots att servicearbetarna verkar vara odödliga kan de stoppas nästan när som helst. Webbläsaren vill inte slösa resurser på en serviceworker som för närvarande inte gör något. Att bli stoppad är inte samma sak som att bli avslutad – servicearbetaren förblir installerad och aktiverad. Den är bara satt i vila. Nästa gång den behövs (t.ex. för att hantera en begäran) väcker webbläsaren den igen.
waitUntil #
På grund av den konstanta möjligheten att bli försatt i vila behöver din serviceworker ett sätt att låta webbläsaren veta när den gör något viktigt och inte känner för att ta en tupplur. Det är här som event.waitUntil()
kommer in i bilden. Den här metoden förlänger livscykeln som den används i och hindrar den både från att stoppas och från att gå vidare till nästa fas i sin livscykel tills vi är redo. Detta ger oss tid att sätta upp cacher, hämta resurser från nätverket osv.
Detta exempel talar om för webbläsaren att vår serviceworker inte är klar med installationen förrän assets
-cachen har skapats och fyllts med bilden av ett svärd:
self.addEventListener("install", event => {
event.waitUntil(
caches.open("assets").then(cache => {
return cache.addAll();
})
);
});
Se upp för det globala tillståndet #
När denna start/stopp sker nollställs serviceworkerns globala omfattning. Så var försiktig så att du inte använder något globalt tillstånd i din service worker, annars blir du ledsen nästa gång den vaknar upp igen och har ett annat tillstånd än vad den förväntade sig.
Tänk på det här exemplet som använder ett globalt tillstånd:
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
Vid varje begäran loggar den här serviceworkaren ett nummer – låt oss säga 0.13981866382421893
. Variabeln hasHandledARequest
ändras också till true
. Nu sitter servicearbetaren stilla en stund, så webbläsaren stoppar den. Nästa gång det kommer en begäran behövs servicearbetaren igen, så webbläsaren väcker den. Dess skript utvärderas igen. Nu är hasHandledARequest
återställd till false
, och favoriteNumber
är något helt annat –0.5907281835659033
.
Du kan inte förlita dig på lagrat tillstånd i en service worker. Att skapa instanser av saker som Message Channels kan också orsaka fel: du får en helt ny instans varje gång servicearbetaren stannar/startar.
Varning: När Chrome DevTools är öppet är start/stopp-beteendet inaktiverat när du arbetar med din service worker-kod. Du kanske inte ens ser buggar som orsakas av att du förlitar dig på globalt tillstånd förrän de har skickats till dina användare.
I Service Workies kapitel 3 visualiserar vi vår stoppade serviceworker som att den förlorar all färg medan den väntar på att bli väckt.
Tänk på din serviceworker som en whippethund. Den är snabb, lojal och fantastisk. Den kommer att hålla sig vid din sida oavsett vad som händer. Men oftast vill den bara sova. Hela tiden. Du måste låta den veta när du vill att den ska vara vaken. Duktig hund!
Samman, men åtskilda #
Din sida kan bara styras av en servicearbetare åt gången. Men den kan ha två service workers installerade samtidigt. När du gör en ändring i koden för din serviceworker och uppdaterar sidan redigerar du egentligen inte alls din serviceworker. Tjänstearbetare är oföränderliga. Du gör istället en helt ny. Den här nya servicearbetaren (låt oss kalla den SW2) kommer att installeras, men den kommer inte att aktiveras ännu. Den måste vänta på att den nuvarande servicearbetaren (SW1) ska avslutas (när användaren lämnar webbplatsen).
Måla med en annan servicearbetares caches #
Under installationen kan SW2 få saker och ting konfigurerade – vanligtvis skapa och fylla på caches. Men varning: den nya tjänstemedarbetaren har tillgång till allt som den nuvarande tjänstemedarbetaren har tillgång till. Om du inte är försiktig kan din nya väntande servicearbetare verkligen ställa till det för din nuvarande servicearbetare. Några exempel som kan ställa till det för dig:
- SW2 kan radera en cache som SW1 använder aktivt.
- SW2 kan redigera innehållet i en cache som SW1 använder, vilket gör att SW1 svarar med tillgångar som sidan inte väntar på.
Skip skipWaiting #
En serviceworker kan också använda den riskabla skipWaiting()
-metoden för att ta kontroll över sidan så snart den är klar med installationen. Detta är i allmänhet en dålig idé om du inte avsiktligt försöker ersätta en felande servicearbetare. Den nya servicearbetaren kan använda uppdaterade resurser som den aktuella sidan inte förväntar sig, vilket leder till fel och buggar.
Start rent #
Sättet att hindra dina servicearbetare från att klampa in i varandra är att se till att de använder olika cacheminnen. Det enklaste sättet att åstadkomma det är att versionera cache-namnen som de använder.
const version = 1;
const assetCacheName = `assets-${version}`;
self.addEventListener("install", event => {
caches.open(assetCacheName).then(cache => {
// confidently do stuff with your very own cache
});
});
När du distribuerar en ny tjänstemedarbetare, kommer du att bumpa version
så att den gör vad den behöver med en helt separat cache från den tidigare tjänstemedarbetaren.
Slutar rent #
När tjänstemedarbetaren når activated
-tillståndet, vet du att den har tagit över, och att den tidigare tjänstemedarbetaren är överflödig (dvs, inte längre behövs). Vid denna tidpunkt är det viktigt att städa upp efter den gamla servicearbetaren. Det respekterar inte bara dina användares lagringsgränser för cache, utan kan också förhindra oavsiktliga buggar.
Metoden caches.match()
är en ofta använd genväg för att hämta ett objekt från vilken cache som helst där det finns en matchning. Men den går igenom cacherna i den ordning de skapades. Så låt oss säga att du har två versioner av en skriptfil app.js
i två olika cacher – assets-1
och assets-2
. Din sida förväntar sig det nyare skriptet som är lagrat i assets-2
. Men om du inte har raderat den gamla cachen kommer caches.match('app.js')
att returnera den gamla versionen från assets-1
och troligen förstöra din webbplats.
Allt som krävs för att städa upp efter tidigare service workers är att radera all cache som den nya service workers inte behöver:
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);
}
});
);
});
);
});
Förhindra att dina service workers klämmer in varandra kräver lite arbete och disciplin, men är värt besväret.
Tänk på kombinationen av din service worker och din webbplats som en installerbar app. Varje version bör fungera. Varje version ska vara separat från de andra. Föreställ dig hur buggigt ett spel skulle bli om utvecklaren av misstag släppte en patch som använde ny spellogik men föråldrade tillgångar. Du skulle rasa på forumen så snabbt! Håll dina appversioner snygga & rena.
Servicemedarbetarnas tankesätt #
Att sätta sig in i rätt tankesätt när du tänker på servicemedarbetare kommer att hjälpa dig att bygga dina med självförtroende. När du väl har fått grepp om dem kommer du att kunna skapa otroliga upplevelser för dina användare.
Om du vill förstå allt detta genom att spela ett spel så har du tur! Gå och spela Service Workies där du lär dig servicearbetarens sätt att arbeta för att kunna döda offline-djuren.