Intro to service workers

So you probably landed here because you do not use service workers in your app and you want to know how they
can help you.

No need to worry. We go through all the service worker API in detail.

Tl;dr

In this post, I try to outline the main functionality of service workers, and it’s lifecycle. I talk about why I think that every website or web app should make use of service workers. You find all the resources for this service worker tutorial on GitHub.

Table of Contents

  1. Why can you use service workers for you website?
  2. What is a service worker?
  3. What are the use cases for Service Workers?
  4. Service workers events and lifecycle?
  5. How to register service worker?
  6. Event handlers of service workers
  7. Service worker and Push Notifications
  8. How to unregister service worker
  9. How to debug a service worker
  10. Conclusion

1. Why can you use service workers for you website?

In my mind, there are only a few reasons why you don’t need a service worker. If your app never requires a network request and your assets are stored locally on your device. Maybe this is a rare example, but if you don’t need a network request,you can not intercept anything.

In any usual case of a web app or website, you can use service workers. Your users will thank you for it and maybe even buy your excellent product because you delivered your site fast.

2. What is a service worker?

A service worker is a script that your browser runs in the background. The primary use case is caching by programmatically intercept network requests, but this is not the only feature, with a service worker you can implement push notifications and background sync.

By allowing us developers to intercept all network requests, we can go offline with our web apps and websites. We could do this before with AppCache, but since this API is deprecated, you should go with service workers.

image showing how a service worker intercept the network request

But not only should you use service workers. In my opinion, every website or web app should have a service worker installed. At least to cache resources to make features of your site faster or accessible while you’re on a slow network. Thus service workers are not just there for implementing your app as a PWA.

What is a service worker technically: It’s a special kind of JavaScript web worker, so it has no access to the DOM. It’s like an event-driven programmable network proxy. Service workers use promises in the background. So you should have a minimum of knowledge of the Promises API.

One of the main advantages of a service worker is that the script runs on a different thread than the regular web page. So no user interaction is blocked by the running worker. A service worker only works over HTTPS or on localhost for security reasons.

3. What are the use cases for Service Workers?

Use cases for service workers are primary caching. But they offer much more features for us web developers. Caching is probably the most used feature and the feature you should use. I think this is not a matter of option. By implementing caching with a service worker, you can make the user interaction with your site faster and make it accessible even if the user is not online.

If you cache your assets with a service worker you need to intercept the network requests your app is making. Besides responding to the cached resource, you could even change the whole file you respond. A simple example would be that your application requests an image of a dog, and you send an image of a lama from the service worker. But thinking about this feature brings us to the availability to manipulate any request. I think this is a powerful feature for any developer.

The next use case is the most annoying features: Push Notifications. These are two technologies: Push and Notification. Notification is the one we all know and decline every day. You can send notifications to your user using a service worker. I don’t mean that the technology behind Notifications is terrible, but more the way we developers implement them or how the requirements engineers like to have them: An possibility to send notifications to our users.

We can trigger him even if he is not on our site. But the main problem is that this is super annoying. I do not even think about what an application could send me if they ask me 20 milliseconds after I visit their site for permission to send me notifications. We could do this in a much elegant way. First, we should show them our product or content, then what we will send them and finally we should tell our users that we will now ask them for permission.

Push is a much more powerful feature than you think. With a service worker, you can receive push messages from a server. To set up Push is complicated, but then you can send push messages from your server to your client (service worker) even if there is no tab open. You can send the new version of your blog article to your users even if they are not currently on your site and your service worker can cache the new article. The service worker responds to the client with your new article without having to make a real network request.

4. Service workers events and lifecycle

A service worker has a lifecycle that is separate from your web app. The worker is listening to events you can implement. See a simplified lifecycle in the image below. During installing the install event is triggered and while activating the activate event.

If your service worker does not receive any events, it goes into an idle state. It is still available but won’t use many resources after being idle the state changes to terminated. Still, the service worker is available. If it receives functional events, it starts working again.

image showing the lifecycle of a service worker

Events

There are two different kinds of events: Functional events and lifecycle events. The lifecycle events trigger during the installation and activation of your service worker to handle caches, for instance.

You can implement the functional events after the service worker is ready for usage. For example, to intercept and handle network requests.

Lifecycle

image shows the states and events of a service worker

The worker lifecycle contains mainly the following phases or states:

  • Installing
  • Installed
  • Activating
  • Activated
  • Redundant

Installing

This stage is the beginning of the registration of your service worker. You can define the files to cache or any other setup.
During this phase, the install event is triggered.

Installed

The service worker is now ready for your visit and has finished it’s setup. The worker waits in this state to take control of the page from the current service worker.

Activating

If the current state is activating, it means that there no other worker, and the browser fires the activate. It this state, the worker can finish its setup like clearing out old caches.

Activated

The service worker is now ready to handle functional events.

Redundant

This state indicates that another one replaces this worker.

5. How to register service worker?

To install the service worker, we need to register it.
The registration tells the browser to find, download, and parse your service worker after the load event. There might be reasons to start your service worker earlier.

Please check this article for further information.

if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('./service-worker.js')
.then((registration) => {
console.log('congrats. scope is: ', registration.scope);
})
.catch((error) => {
console.log('sorry', error);
});
});
}

If the browser successfully loads your the ./service-worker.js file, it installs it under your domain. Your file contains all event listeners you implemented.

Take care of the scope!

A service worker can only access files that are in its directory or a lower one. You can also change the scope and set it to your favourite folder:

navigator.serviceWorker.register('./service-worker.js', { scope: '/app/' });

6. Event handlers of service workers

Like I said above, we use event handlers to hook into the service worker’s lifecycle and perform our logic.

How to install a service worker - The install event

The browser triggers the install event after the first registration, it is the first event your service worker gets, and it is only called once per service worker. If the worker is updated, the browser calls the install event on a new worker.

You can use this event to cache everything you need before being able to control clients.

Check out the following example. We define a name for the cache with const CACHE_NAME = 'v1' and list all the files we want to store in urlsToCache.

const CACHE_NAME = 'static-v1';
const urlsToCache = ['/', '/js/main.js', '/css/style.css', '/img/image1.jpg'];

Now we are going to implement our event handler for the install lifecycle event. In this case, self is a reference to window where we call the addEventListener function.

self.addEventListener('install', event => {
event.waitUntil(
caches
.open(CACHE_NAME)
.then(cache => {
return cache.addAll(urlsToCache);
});
);
});

Since the caches.open() returns a promise, we need to wrap it with event.waitUntil. After that, all our files are cached in the browser with the Cache API.

How to update service worker cache - The activate event.

The activate event is essential when you are updating your service worker file and want to clean the caches.

After your service worker is installed, it waits for clients to take control of the existing service worker. If this happens, the browser fires the activate event after the install event. For instance, you can use this to remove old caches.

See in the following example how you can hook into this event and use it for your caching strategy. To do so, we add a new array (validCaches) to the worker file.

// added array
const validCaches = ['static-v2']

const CACHE_NAME = 'static-v2';
const urlsToCache = [
'/',
'/js/main.js',
'/css/style.css',
'/img/image1.jpg',
];

self.addEventListener('install', event => {
event.waitUntil(
caches
.open(CACHE_NAME)
.then(cache => {
return cache.addAll(urlsToCache);
});
);
});

In this event handler, we use it to check all caches and delete them if they are not valid.

self.addEventListener('activate', (event) => {
event.waitUntil(
caches
.keys()
.then((keys) =>
Promise.all(
keys.map((key) => {
if (!validCaches.includes(key)) {
return caches.delete(key);
}
}),
),
)
.then(() => {
console.log('V2 now ready!');
}),
);
});

After the service worker is activated, it now has control of the pages, and it can handle events such as fetch, push and sync.

How to intercept network requests - The fetch event

The service worker fetch event fires every time your web app makes any request. In the event handler in your service worker, you can intercept the requests and decide what you send back to your web app.

I this example a cache-first strategy is implemented.

self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
return response;
}
return fetch(event.request);
}),
);
});

In our example above, we tell the browser with event.respondWith that we are going to handle the network request. From that moment on, it is on us the make the client happy.

First, we are checking our cache. With cache.match we search if we have an entry in the cache that matches the specified request.

If it finds nothing, the promise resolves undefined. To have a fallback for that case, we perform the actual network request fetch(event.request) and return.

How to sync data in the bachground - The sync event

The sync event allows you to defer network requests. This feature is useful to ensure that all network requests reach their destination. If the user is online, the sync event fires immediately and perform whatever you implemented. If the user has no connection, the request is deferred and fired as soon as the network is available again.

The sync event allows the deferring of network tasks until the user has connectivity. The feature referrers to an implementation called background sync.
A background sync service worker is useful for ensuring that any network-dependent tasks that a user kicks off during offline mode reach their intended destination when the network is available again.

See the example below. After the service worker is installed, we register one-off sync with registration.sync.register('myFirstSync').

navigator.serviceWorker.register('/sw.js');

navigator.serviceWorker.ready.then((registration) => {
return registration.sync.register('myFirstSync');
});

self.addEventListener('sync', (event) => {
if (event.tag == 'myAwesomeSync') {
event.waitUntil(doSomeStuff());
}
});

After that, we write an event handler to listen to all sync events. In our particular case myAwesomeSync we perform some stuff in doSomeStuff;

self.addEventListener('sync', (event) => {
if (event.tag == 'myAwesomeSync') {
event.waitUntil(doSomeStuff());
}
});

You can check that out in Chrome in your Developer Tools. You see the service worker running in the Application Tab. To test your implemented sync event, check the offline box you see in the Screenshot below and reload your page.

Running service worker in the developer tools console in google chrome

If you want to know more about background sync, you can check out these instructions.

7. Service worker and Push Notifications

With a service worker, you can use commonly mentioned “Push Notifications”. Web Push Notifications are two technologies. It’s Push and Notifications. One handy, the other is annoying people every day hundreds of times.

Notification

Probably you know what’s coming now. I am sure you declined at least one permission today to send you notifications. Many sites request this permission, and most of the time, I just declined it. You can implement these notifications through service workers so you can send the user any notification they might need.

Push API

The Push API is the REAL cool stuff when it comes to Web Push Notifications. Unfortunately, the permission to send Push Messages is the same as to send Notification. So if the user already declined it, you can not use it.

The cool thing about the Browser Push API is that you can use it to communicate between your browser and the server without having an open tab of your web app. Let’s imagine you wrote a blog article about Workbox.js. You published that article, and it gets cached by your service worker.

Next, you edit your article. In a typical case, your browser has to load the updated article and cache it again with your service worker. By using the Push API, your server pushes the updated article right to your service worker in your browser.

The magic even happens if you do not have an open tab of your web app. Your service worker receives the updated article and can update the cache before you even thought about to revisit the blog page.

8. How to unregister service worker

Sometimes things get wrong, and you want to uninstall or unregister your service worker. You can do this manually for your site in the Application Tap of the Developer Tools in Chrome or any other browser that supports service worker. But this will not help your customers if the service worker bug is already in production.

Unregister, a service worker, looks similar to the register you find above.

if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
for (let registration of registrations) {
registration.unregister();
}
});
}

If you deploy this code, it will unregister the current service worker after it finishes its work. But the method can lead to some problems if your page relies on the cached assets. You can read more about this in a post of Chris Live - How to Uninstall, Unregister or Remove a Service Worker [With Example Code].

9. How to debug a service worker

You can debug your service worker just like your regular JavaScript code. You can choose if you want to use console.log, set the debugger keyword in your code or set a breakpoint within the sources-Tab of the Chrome Developer Tools. Don’t be confused by order of your logs, as Chrom handles this different sometimes.

If you want to train to debug, I recommend you to go through the codelab of debugging service workers on the google develops platform.

10. Conclusion

You find all the resources for this article on GitHub. Feel free to check it out, try some stuff or fork it.

In this post, I tried to outline the main functionality of service workers, and it’s lifecycle. I mentioned why I think that every website or web app should make use of service workers.

If you like this article, smile for a moment, share it, follow me, check out my RSS feed and subscribe to my newsletter.

Cheers Marc