Demystifying The Service Worker Lifecycle

PostedDecember 12, 2019 820 views

While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

Introduction

Service workers play a very vital role in Progressive Web Apps (PWA), as they are responsible for offline caching, push notifications, background sync etc. In this article, we’ll be demystifying the service worker lifecycle and what can be done at each stage of the lifecycle.

For effective use of service worker, an understanding of the service lifecycle is essential. The service worker lifecycle consists of mainly 3 phases, which are:

  • Registration
  • Installation
  • Activation

Let’s go over each of them.

Registration

A service worker is basically a JavaScript file. One thing that differentiate a service worker file from a normal JavaScript file, is that a service worker runs in the background, off the browser’s main UI thread. Before we can start using service worker, we must register it as a background process. This is the first phase of the lifecycle. Since service workers are not yet supported in all browsers, we must first check to make sure the browser supports service workers. Below is a code we can use to register a service worker:

app.js
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
    .then(function (registration) {
        console.log('Service worker registered!');
    })
    .catch(function (err) {
        console.log('Registration failed!');
    })
}

First, we check if the browser supports service workers, that is, if the navigator object has a serviceWorker property. Only when it’s supported would we register the service worker. The register() method takes the path to the service worker script and returns a promise.

At the point of registering a service worker, we can also define the scope of the service worker. The scope of a service worker determines the pages that the service worker can control. By default, the scope is defined by the location of the service worker script.

app.js
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js', {
        scope: '/blog/'
    })
    .then(function (registration) {
        console.log('Service worker registered!');
    })
    .catch(function (err) {
        console.log('Registration failed!');
    })
}

In addition to accepting the path to the service worker script, the register() method can also accept an optional object, where we can define the scope of the service worker. Here, we define the scope of the service worker to /blog/, which will limit the service worker to only the blog directory.

Installation

The fact that a service worker has been successfully registered doesn’t mean it has been installed. That’s where the installation phase of the lifecycle comes into play. Upon successful registration of the service worker, the script is downloaded and then the browser will attempt to install the service worker. The service worker will only be installed in either of these cases:

  • The service worker hasn’t been registered before
  • The service worker script changes (even if it’s by one byte).

Once a service worker has been installed, an install event is fired. We can listen for this event and perform some application0-specific tasks. For example, we could cache our application’s static assets at this point:

sw.js
const assetsToCache = [
    '/index.html',
    '/about.html',
    '/css/app.css',
    '/js/app.js',
]

self.addEventListener('install', function (event) {
    event.waitUntil(
        caches.open('staticAssetsCache').then(function (cache) {
              return cache.addAll(assetsToCache);
        })
      );
});

Here, we are using the open() method of the Cache API, which accepts the name of the cache (staticAssetsCache in this case) to either open (if it already exists) or create and returns a promise. Once the promise is resolved, that is, inside the then(), we again make use of the addAll() of the Cache API, which accepts an array of URLs to cache. Since the open() method will return a promise, we need wrap it inside event.waitUntil(), which will delay the installation of the service worker untill the promise is resolved. If the promise is rejected, the install event fails and the service worker will be discarded.

Activation

If the installation was successful, the service worker enters an installed state (though not yet active), during which it waits to take control of the page from the current service worker. It then moves on to the next phase in the lifecycle, which is the activation phase. A service worker is not immediately activated upon installation. A service worker will only be active (that is, be activated) in any of these cases:

  • If there is no service worker currently active
  • If the self.skipWaiting() is called in the install event handler of the service worker script
  • If the user refreshes the page

An example of using the skipWaiting() method to activate a service worker can look like below:

sw.js
self.addEventListener('install', function (event) {
    self.skipWaiting();

    event.waitUntil(
           // static assets caching
      );
});

An activate event is fired upon a service worker being active. Like the install event, we could also listen for the activate event and perform some application specific tasks. For for example, clearing out the cache:

sw.js
const cacheVersion = 'v1';

self.addEventListener('activate', function (event) { 
    event.waitUntil( 
        caches.keys().then(function (cacheNames) {
            cacheNames.map(function (cacheName) {
                if (cacheName.indexOf(cacheVersion) < 0) { 
                    return caches.delete(cacheName);
                   } 
                }); 
            });
        }) 
    ); 
});

The snippet above loops through all the named caches and deletes any existing if the cache does not belongs to the current service worker.

Once the service worker has been activated, it now has full control of the pages. With the service worker active, it can now handle events such as fetch, push and sync.

sw.js

self.addEventListener('fetch', function (event) {
    event.respondWith(caches.match(event.request))
    .then(function (response) {
        return response || fetch(event.request);
    });
});

If the service worker after being active, does not receive any of the functional events mentioned above, it goes into an idle state. After being idle for some time, the service worker goes into a terminated state. This does not mean the service worker has been uninstalled or unregistered. In fact, the service worker will become idle again as soon as it begins to receive the fuctional events.

Below is a visual summary of the service worker lifecycle:

The Service Worker Lifecycle

Conclusion

In this article, we looked at the service worker lifecycle, the events that are emitted at end phase of the lifecycle. Also, we looked some possible things we could with a service worker by hooking into some of these events.

0 Comments

Creative Commons License