In an earlier article of mine, I talked about 4 essential things every PWA must have, which service worker happens to be part of. Service worker plays a very vital role when it's comes to Progressive Web Apps (PWA), as it is 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 service worker runs in the background. Before we can start using service worker, we must register it as a background process. This is the first phase of the phase of the lifecycle. Since service worker is not currently supported in all browsers yet. When registering a service worker, we must first check to make sure the browser supports service worker. 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 support service worker, that is, if the navigator object has a serviceWorker property. Only when it's supported that we would register the service worker. The register() 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 service worker script, the register() 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 application specific tasks. For example, we could cache our application 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() 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() 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() to active 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

So 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.

I hope you find this article helpful and let's continue the conversation in the comments section below.