Understanding service worker life cycle

Vipul Nema
6 min readJul 21, 2019

--

source

What is service worker — It is a proxy worker between browser and server that intercept network request and respond accordingly, to provide nice offline user experience using browsers resource like — Cache, IndexDb, etc.

Hello Friends, Here I am going to share my knowledge that I got while integrating service worker for resource caching (JS, CSS, fonts, images, etc) in my application.

To getting a better understanding of service workers, below are the things that you should be familiar with before you go further.

  1. Web Worker( https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers)
  2. Fetch( https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
  3. Browser Cache ( https://developer.mozilla.org/en-US/docs/Web/API/Cache)
  4. Promise( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
  5. Https Cache-control headers( https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching)
  6. Old Caching mechanism — AppCache ( https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache)

MDN and Google dev are great sources of getting deep understanding. Below I am briefly some of the points.

Small demo

The main benefit of using service workers than old methods is that it runs on a different thread to the main JavaScript that powers your app, so it is not blocking.

A service worker is run in a worker context: therefore it has no DOM access, It is designed to be fully async; as a consequence, APIs such as synchronous XHR and localStorage can’t be used inside a service worker. Service workers only run over HTTPS or localhost, for security reasons( man in the middle attacks).

If Offline than serve from cache.

Service worker life cycle — The below image from MDN nicely explains each step of the service worker’s life cycle.

Every new thing seems tough in starting but when you got a better understanding of how it works conceptually it is so simple for you. For integrating this event-based logic that works in a different thread and handles your network calls. So for using it, we have to first register it. then it would start handling our page.

Registering incorporate below steps

  1. download file — it is done by register method.
  2. install event — it is like a class constructor that fires once per service worker registration.
  3. active event — helpfull in maintaining and clearing cache

After a successful registration service worker is ready to control page under its scope by ‘fetch ’event.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — —

For registering the service worker you simply call serviceWorker.register method and give a path of the file where all service events(install, active, fetch, push, sync) are written which handle the things.

navigator.serviceWorker.register('./sw-scope/sw.js', {scope: './sw-scope/'})
.then((reg) => {
// registration worked
console.log('Registration succeeded. Scope is ' + reg.scope);
});

Scope — If we register the service worker file at /route1/sw.js, then the service worker would only see fetch events for pages whose URL starts with /route1/ (i.e. /route1/page1/, /route1/page2/). These pages are also called controlled pages.

While the registration process, install and active events fired in the background. These events run only once per the new update of service worker registration. After successful registration, fetch and other events fires while network request. Service worker remains ideal when it is not required.

source — https://developers.google.com/web/fundamentals/primers/service-workers/

Install — Inside of our install callback, we can do any action like below

Open a cache, Cache our files, Confirm whether all the required assets are cached or not.

self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
// Promise like cahce.open
);
});

caches.open() promise to create and open a browser cache with passed cache name, after which we call cache.addAll()and pass in our array of files. For more details — https://developer.mozilla.org/en-US/docs/Web/API/Cache/addAll

Active — if install resolved with success then active event fires. After install, even a service worker is almost ready to work. But while updating the service worker logic, if you want to do some stuff before it starts to handle fetch events those work we can do here like below.

One common task that will occur in the activate callback is cache management. The reason you'll want to do this in the activate callback is that if you were to wipe out any old caches in the install step, any old service worker, which keeps control of all the current pages, will suddenly stop being able to serve files from that cache.

self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
// Return true if you want to remove this cache,
// but remember that caches are shared across
// the whole origin
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
});

Fetch — After a service worker is installed and the user navigates to a different page or refreshes, the service worker will begin to receive fetch events

You can attach a fetch event listener to the service worker, then call the respondWith()method on the event to hijack our HTTP responses and update them with your own magic. for example if index.html makes a cross origin request to embed an image, that still goes through its service worker.

self.addEventListener('fetch', (event) => {
event.respondWith(
// magic goes here
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});

How to update service worker —

The latest browser checks each time whether the service worker file got updated or not, not matters what cache-control headers are there. If the file is not updated then old SW continue handle page but if changes then new service worker got register, but this is not guaranteed on old browsers versions so to update the service worker logic, make sure the file that you register and where all evens handling logic is written should not be cached in browser or server like at the backend server or at cdn handles.

If your application is not the offline app. which means you are not storing index.hml file in the cache, then you can easily update service worker file with versioning too. but it is not a good way.

new service worker logic only activated when there are no longer any pages loaded that are still using the old service worker. you can control this by self.skipWaiting.

Google workbox — Google workbox is a tool to easily integrate various caching strategies using service worker for PWA(progressive web app)

Opaque response — Be careful, if you store js script and CSS link, etc response in the browser cache, by default fetch request mode for these are no-cors. In that case, the response would be opaque which take 90 times more padding data in the cache. So 1MB data would be calculated as 90–100 MB data. so you may get an error like — quota exceeded.

More details — https://developers.google.com/web/tools/workbox/guides/storage-quota

solution

  1. use the crossorigine attribute in each HTML resource, then default mode would be cors. you can add it webpack build too using this option. ( https://developers.google.com/web/tools/workbox/)
  2. Convert fetch request mode ’no-cors’ to ‘cors’ manually, if server supporting cors enabled for your domain. most CDN are corn enabled.

Unregister/Uninstall service worker — In the project development lifecycle, there is a phase after development and QA, when you deploy or publish your application to users. On critical thing developers always do know how to revert changes in case of any bug.

The same is the case in registering service worker. Because it hijack your application, so if any buggy code is present inside the service worker you would have to unregister it with code.

navigator.serviceWorker.getRegistration('/app').then(function(registrations) {
for (let registration of registrations) {
registration.unregister().then(function(boolean) { //if boolean = true, unregister is successful, //you have to delete cache if want }); }});

--

--

Vipul Nema

Frontend Web Developer | React Js| Javascirpt | Web Performance expert