In our last series about prefetching we looked at what prefetching is, and also we kind of rebuilt the quicklink library, a data considerate, idle time prefetching library developed by Addy Osmani to demonstrate how prefetching works. W

We learned how we can prefetch resources using different strategies, how to check if the user is on a 2g network or in a data-saver mode not to waste his precious data and how we trigger the prefetch of a href link when it comes into view using the IntersectionObserver API.

In this post, we will continue our prefetching journey to see how we can do the same inside service workers, to build more efficient, much faster apps with a better experience for our users.

What are Service Workers?

Service Workers are one of the new features introduced in browsers today. They help us run a script in a separate thread other than the DOM thread that renders our views.

JS is single-threaded, which means it runs in a single thread. You cant spawn another thread like we usually do in Java and C/C++. So, trying to perform a CPU intensive operation in the DOM thread will hamper the viewers’ experience.

Our app will feel stuck and unresponsive, whereas it is running under the hood, it’s just that an operation is taking too much time to reach the renderer.

In Chrome, the v8 JS engine spawns two isolates: one isolate to run the DOM and the other to run the Workers. An isolate is a single VM instance with its own heap. So spawning two isolates, creates two instances and two heaps.

DOM isolate Workers isolate

-------------|---------------

heap | heap

-------------|---------------

|DOM | |Worker |

|APIs | |APIs |

| | | |

| | | |

| | | |

| | | |

These two run concurrently and can only communicate with each other by posting messages. They can achieve that by reading and writing from a register or memory space shared between the threads. So to avoid clogging up your browser view by running expensive ops in the DOM thread, it is advised to move it over to the Workers. When you move to the Workers the two will run side by side without any lag or unresponsive page in your browser, when the Worker is done with the op it posts a message to the DOM thread with the data telling it, it is done and you can update the DOM. When the threads are created, it is left for the machine OS to know when and how to switch threads for execution, the process brings to us multi-parallelism.

You see the concept is based on multi-threading, which allows chunks or blocks of code to run parallel to each other, each with their own memory space with an exclusion lock.

Lifecycle in Service Workers

A service worker has three lifecycles:

download and registration

install

fetch

activate

download and registration

Before we use a Service worker in our website, we need to know if the browser supports service:

if ('serviceWorker' in navigator) {

// ...

} else {

console.log('service worker unavailable');

}

If the browser support service worker, we download and register our service worker file:

if ('serviceWorker' in navigator) {

// ...

navigator.serviceWorker.register('service-worker.js')

.then((serviceWorker)=>{

console.log('service worker registration successful')

})

.catch(()=>{

console.error('service worker registration failed')

})

} else {

console.log('service worker unavailable');

}

Our service worker is in service-worker.js file. We register by calling the navigator.serviceWorkerregister(...) function. It returns a Promise, it resolves when the registration succeeds we catch that by calling the .then(...) function it logs service worker registration successful if the registration succeeds. The Promise will reject if the registration fails, so use .catch(...) to catch the error, it logs service worker registration failed to tell us the registration failed.

Service Worker scope Service workers have a scope, that is the domain where files will be served from. By default, the scope of a service worker is the path it is located. What do we mean by domain? It simply means a path or folder from our website project where network requests will be intercepted and files will be served from.

If we have our project like this:

/serv

/pages/

/app/

sw.js

Our service worker file is located at /serv/app/ . This is the scope of the service worker. Any request made to URL relative to /serv/app/ will be intercepted and served by the service worker. A request like /serv/pages/fag will be ignored by the service worker. To define our own scope, we pass the path we want as a second arg to register(...) :

if ('serviceWorker' in navigator) {

// ...

navigator.serviceWorker.register('service-worker.js',{

scope: '/app/'

})

.then((serviceWorker)=>{

console.log('service worker registration successful')

})

.catch(()=>{

console.error('service worker registration failed')

})

} else {

console.log('service worker unavailable');

}

install

Once the service worker registration has succeeded and completed, the install event is fired. At this stage any service worker previously installed will be in charge. This service worker will not be intercepting any requests at this stage.

This event is fired when there is a change in the service worker file.

self.addEventListener('install', function(evt) {

// perform initialization tasks here

});

Here, self refers to the serviceWorker.

activate

This is the stage where our service worker becomes active, intercepting requests and serving pages. The event is fired the first time our service worker becomes active.

self.addEventListener('activate', function(evt) {

// perform activation tasks here

});

fetch

This even is fired when a network request is made withni our service worker: