The Service Worker Lifecycle

Nil Seri
5 min readFeb 12, 2024

Service Worker Scope, Updates and Lifecycle

Photo by Ernie Tan Ah Kow on Unsplash

What Is a Service Worker?

A service worker is a piece of JavaScript code that works as a single controller & network proxy for all the instances of our application — all the browser windows or tabs share the same active Service Worker.

It is mostly used for Progressive Web Applications (PWAs) or a better performance via caching. When an app requests a resource covered by the service worker’s scope, including when a user is offline, the service worker intercepts the request, acting as a network proxy. It can then decide if it should serve the resource from the cache via the Cache Storage API.

Scope

The default scope of a service worker registration is “./” relative to the script URL.
You can register a service worker for a different path but be aware that it can only control pages of your app that are in-scope.
You can detect if a client is controlled via “navigator.serviceWorker.controller” which will be null or gives you a service worker instance.

How Does It Understand There Is An Update?

Your service worker is considered updated if it’s byte-different to the one the browser already has.

An update is triggered if any of the following happens:
- A navigation to an in-scope page.
- Functional events such as push and sync, unless there’s been an update check within the previous 24 hours.
- Calling .register() only if the service worker URL has changed — you should avoid changing the worker URL.

To ensure the browser has all it needs to reliably detect changes to a service worker’s contents, don’t tell the HTTP cache to hold onto it, and don’t change its file name.

Here is an example of what will happen if you change the URL of your service worker script:

- index.html registers sw-v1.js as a service worker.
- sw-v1.js caches and serves index.html so it works offline-first.
- you update index.html so it registers your new and shiny sw-v2.js.
If you do the above, the user never gets sw-v2.js, because sw-v1.js is serving the old version of index.html from its cache. You’ve put yourself in a position where you need to update your service worker in order to update your service worker.

When Do Updates Happen?

Simple refreshing may not be sufficient to make room for the new Service Worker to take over, even if there’s only one tab of our application running. This is because browsers do not unload the earlier instance of the website immediately when we request the refresh — for some time the new instance being loaded exists simultaneously in the memory next to the previous one, being unloaded.

Hard-refresh (Control+Shift+R) is sufficient (because it bypasses the Service Worker), but we can’t expect our users to use it for the ordinary browsing.

By default, once successfully installed, the updated worker will wait until the existing worker is controlling zero clients — “installed / waiting” state. Activation occurs when all open windows/tabs for the relevant website are closed. That is how the browser ensures that only one version of your service worker is running at a time.

self.skipWaiting()
It prevents the waiting, meaning the service worker activates as soon as it’s finished installing (the waiting phase is skipped). We may call it at the end of install event handling.

Imagine what happens if we are using lazy-loading techniques. Depending on the strategies we use, we might end up not being able to serve the expected asset anymore or we serve assets in incompatible version — it has a high probability of messing things up. You may need to reconsider lazy loading strategy (PreloadAllModules in Angular) or use of self.skipWaiting.

clients.claim()
If you’re registering a service worker for the first time on a page, then the service worker will be installed and activated but it will not become the controller of the page because the page was not requested through a service worker. You can fix this by calling clients.claim() in the activate handler. This simply means that you wont have to refresh the page before you see the effects of the service worker.

As you can see, self.skipWaiting and clients.claim are designed to solve different problems.

The service worker lifecycle

A service worker has 3 possible phases. These are:

  • Registration
  • Installation
  • Activation

Between those phase changes, there are also 6 different states it can enter such as:

  • Parsed: When a service worker is attempted to register, the browser tries to parse the script. If parsing and other prerequisites (same origin, HTTPS or localhost, etc.) are met, a service worker is then registered.
  • Installing: One parsing is done, a service worker is moved to the installing state by the browser. You can add an event listener for “install” event to perform some actions during installing. You can use an “event.waitUntil()” method so that installation is suspended until the promise within it is resolved. Here, usually caches are opened and static files are cached. If the promise is rejected, service worker fails to complete install and becomes Redundant.
  • Installed / Waiting: After installation is successful, a service worker moves to “installed/waiting” state. This means it is valid but not active yet. You can use “registration” object to check for the existence of “waiting” child object. Usually, a new version of your application is informed to your users, here. If the user approves to install the update, you can call window.location.reload() and self.skipWaiting() consequently.
  • Activating: A service worker enters into “activating” state if
    - there is no previously installed service worker before
    - self.skipWaiting() method is called
    - user has navigated away from the page — releasing the previous active worker
    - a specified period of time has passed — releasing the previous active worker
    You can add an event listener for “activate” event to clear files from old caches. You can use an “event.waitUntil()” method so that activation is suspended until the promise within it is resolved. Functional events (fetch — intercepts network requests, push — push notifications, sync — runs tasks once a stable network connectivity is established, etc.) will be buffered until the promise resolves. If the promise is rejected, service worker fails to complete activation and becomes Redundant.
  • Activated: A service worker is now active and in full control. You can use “registration” object to check for the existence of “active” child object. Usually, “fetch” —( listens and intercepts network requests) and “message” —( handles postMessages received from document) events are listened, here.
  • Redundant: A Service Worker can become redundant if:
    - installing event fails
    - activating event fails
    - a new Service Worker replaces it as the current active service worker
Default Lifecycle of a Service Worker
Service Worker States

Happy Coding!

--

--

Nil Seri

I would love to change the world, but they won’t give me the source code | coding 👩🏻‍💻 | coffee ☕️ | jazz 🎷 | anime 🐲 | books 📚 | drawing 🎨