Have you ever wondered how some websites seem to keep working even when you’re offline? The secret is simple: these websites have service workers.

Service workers are the key technology behind many of the native app-like features of modern web applications.

What Are Service Workers?

Service workers are a specialized type of JavaScript web workers. A service worker is a JavaScript file that functions a bit like a proxy server. It catches outgoing network requests from your application, letting you create custom responses. You might, for example, serve cached files to the user when they're offline.

Service workers also let you add features like background sync to your web applications.

Why Service Workers?

Web developers have been trying to expand the capabilities of their apps for a long time. Before service workers came along, you could use various solutions to make this possible. A particularly notable one was AppCache, which made caching resources convenient. Unfortunately, it had issues that made it an impractical solution for most apps.

AppCache seemed to be a good idea because it allowed you to specify assets to cache really easily. However, it made many assumptions about what you were trying to do and then broke horribly when your app didn't follow those assumptions exactly. Read Jake Archibald's (unfortunately-titled but well-written) Application Cache is a Douchebag for more details. (Source: MDN)

Service workers are the current attempt to reduce the limitations of web apps, without the drawbacks of tech like AppCache.

Use Cases for Service Workers

So what exactly do service workers let you do? Service workers allow you to add features that are characteristic of native apps to your web application. They can also provide a normal experience on devices that don't support service workers. Apps like this are sometimes called Progressive Web Apps (PWAs).

Here are some of the features service workers make possible:

  • Letting the user keep using the app (or at least parts of it) when they're no longer connected to the internet. Service workers achieve this by serving cached assets in response to requests.
  • In Chromium-based browsers, a service worker is one of the requirements for a web app to be installable.
  • Service workers are necessary for your web application to be able to implement push notifications.

The Lifecycle of a Service Worker

Service workers may control requests for a whole site, or only a portion of the site's pages. A particular webpage can only have one active service worker, and all service workers have an event-based lifecycle. The lifecycle of a service worker generally looks like this:

  1. Registration and download of the worker. The life of a service worker starts when a JavaScript file registers it. If the registration is successful, the service worker downloads, and then starts running inside a special thread.
  2. When a page controlled by the service worker is loaded, the service worker receives an 'install' event. This is always the first event a service worker receives, and you can set up a listener for this event inside the worker. The 'install' event is generally used to fetch and/or cache any resources the service worker needs.
  3. After the service worker finishes installing, it receives an 'activate' event. This event allows the worker to clean up redundant resources used by previous service workers. If you are updating a service worker, the activate event will only fire when safe to do this. This is once there are no loaded pages still using the old version of the service worker.
  4. After that, the service worker has full control of all pages which were loaded after it was registered successfully.
  5. The last phase of the lifecycle is redundancy, which occurs when the service worker is removed or replaced by a newer version.

How to Use Service Workers in JavaScript

The Service Worker API (MDN) provides the interface that allows you to create and interact with service workers in JavaScript.

Service workers primarily deal with network requests and other asynchronous events. As a result, the service worker API makes heavy use of Promises and asynchronous programming.

To create a service worker, the first thing you need to do is to call the navigator.serviceWorker.register() method. Here's what that might look like:

        if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').then(function(registration) {
    console.log('Service worker registration succeeded:', registration);
  }).catch((error) => { console.log('Service worker registration failed:', error); });
} else {
  console.log('Service workers are not supported.');
}

The outermost if block performs feature detection. It ensures service worker-related code will only run on browsers that support service workers.

Next, the code calls the register method. It passes it the path to the service worker (relative to the site's origin) to register and download it. The register method also accepts an optional parameter called scope, which can be used to limit the pages controlled by the worker. Service workers control all of an application's pages by default. The register method returns a Promise that indicates whether the registration was successful.

If the Promise resolves, the service worker registered successfully. The code then prints an object representing the registered service worker to the console.

If the registration process fails, the code catches the error and logs it to the console.

Next, here's a simplified example of what the service worker itself might look like:

        self.addEventListener('install', (event) => {
  event.waitUntil(new Promise((resolve, reject) => {
    console.log("doing setup stuff")
    resolve()
  }))
  console.log("Service worker finished installing")
})
 
self.addEventListener('activate', (event) => {
  event.waitUntil(new Promise((resolve, reject) => {
    console.log("doing clean-up stuff!")
    resolve()
  }))
  console.log('activation done!')
})
 
self.addEventListener('fetch', (event) => {
  console.log("Request intercepted", event)
});

This demo service worker has three event listeners, registered against itself. It has one for the 'install' event, the 'activate' event, and the 'fetch' event.

Inside the first two listeners, the code uses the waitUntil method. This method accepts a Promise. Its job is to make sure the service worker will wait for the Promise to resolve or reject before moving on to the next event.

The fetch listener fires whenever a request is made for a resource the service worker controls.

The resources controlled by a service worker include all the pages it controls, as well as any assets referenced in those pages.

Enhance Your Web Apps With Service Workers

Service workers are a special kind of web worker that serve a unique purpose. They sit in front of network requests to enable features like offline app access. Using service workers in a web application can vastly improve its user experience. You can create service workers, and interact with them, using the service worker API.