Service Workers Refresher

Arausi Oghenekaro Daniel
8 min readJan 27, 2020

Introduction

Service workers have very quickly become more than a trendy peripheral, it is an essential requirement in creating seamless and great experiences both online and offline. As of today it is very much supported by most browsers even IE 10 [1].

Case for Service Workers:

There is a general assumption of a contiguous web experience across multiple end-users in various countries bound by differing network constraints. We easily forget that the point of the web amongst many is to create resource points on different subject matters, making it easily accessible while maintaining the integrity of the data in shape, quality, and experience. Without this assurance, one could argue it’s not any better than our old love of post mails and newspapers. Which could very well guarantee a much more contiguous experience without the very well-favored and absolute millennial essential “real-time” experience. If there was a way to mimic this possibility on the web, an illusion of sorts to cater to our geo-economic differences.

Javascript as we all know is single-threaded, any blocking operation within this thread would definitely lead to serious problems and ultimately a bad user experience.

The conventional contrived example below, to further explain a blocking operation:

HTML

<input type="text" placeholder="name" /> 

JS

input = document.querySelector("input");input.addEventListener("input", function(event){
// do some stuff here
alert("block")
})
Browser Implentation of a blocking Scripts
Image[1]: Blocking Image

From the above, the blocking operation happens when the user types in the input box. At the same time, the alert function is called which immediately halts the browser. This blocking operation obviously contrived in this case could be something else ranging from logging user behaviour entries to a monitoring tool or what not.

Service workers are a type of web workers in the web browser stack[2]. The Idea mainly is the run a separate thread from the main UI thread where the web application is currently running. As you may guess this could easily be useful in areas where complex business logic may be offloaded to be processed, while the main application is left unfettered.

One bugging security alarm that may be ticking off in mind is the possibility of a DOS attack by spawning multiple threads from the browser of a user. However, no browser implementation of web workers promises a new thread spawned for every web worker subprocess initialization [3]. But what is of great certainty is the fact that web workers will be assigned to a single thread, absolutely different from the main UI thread that runs the current web application[3].

Web Workers are categorized into two main types:

  1. Shared Worker
  2. Dedicated worker

SHARED WORKER

A shared worker can be utilized across multiple pages in an application. Or one application. Communication can happen across pages with this worker.

DEDICATED WORKER

A dedicated worker will be utilized only in one page and terminated when the page closes, if several tabs were opened on that same page, multiple dedicated service workers will be spawned. Each of them will ultimately be terminated when they are closed.

One major difference between a service worker and a web worker is the fact that a service worker is kept alive, running in the background even after the page/application using it has been closed. this doesn’t also imply that service workers are immortals, they are still terminated after a period of idle time(this is handled by the browser mostly).web workers in general are restricted to having only network access, so conventional DOM manipulation is not possible in worker scripts.

COMMUNICATION BETWEEN PAGES AND WEB WORKERS

Transferring data between web workers and pages happen through a process structured cloning algorithm [4]. This is basically “stringifying” data from either side and parsing it in the other. This is very similar to operations like writing to indexDB, localStorage, sessionStorage, etc. However, it’s important to note that like the others, functions and Errors can’t be transferred back and forth. This approach comes with obvious memory shortcomings. an alternative would be to leverage ES7 shared Array Buffer[5] to prevent copying and pasting data back and forth pages and web workers.

WEB WORKER DEEP DIVE

Quick dive into workers in detail, consider the code example below:

Image[1]: Fibonacci solved with Recursion

suppose we intend to run some complex logic in our application, which could be blocking. in our case generating a fibonacci series in our browser, in response to a button click.The fibonacci function leverages recursion which is not the best implementation, but it perfectly suits our case.

Image[2]: Fibonacci solved with Recursion

The worker scripts above listen for a message from the page using the onmessage method that receives an event argument with a data property in it. the data property contains information sent from the page scripts.

The genFib function references the startFib function above that expects a “n” parameter to compute the Fibonacci number. setTimeout placed in the genFib function ensures that the next Fibonacci number is generated after the self.postMessage has been called and cleared of the event loop.

Image[3] Page Scripts

HTML:

<h1>Contrived Example</h1><button class="start-fib">Start</button><div class="fib-body"><ul></ul></div>
image[4] Total output

From the above, the Fibonacci function is called and processed in a separate thread thanks to the web worker. After the process is done the web worker returns an output via an event emitted after every nth term in the sequence is generated. This process doesn’t distort the main UI thread as I try to scroll and highlight the screen. The Fibonacci process happens separately oblivious to what the user currently does.

SERVICE WORKERS

Service workers are effectively still web workers with the major distinguishing feature being the fact that it has a longer lifecycle in comparison to normal web workers. it can still exist even after the page has been closed.

Service workers are proxies that sit on our browser. It interfaces with every request that goes back and forth the browser. even if the browser tab isn’t open it can still interact with the server on the browser’s behalf.

Using Service Workers

A very good tip when using service workers is understanding the lifecycles of a service worker. a service worker is in one of three(3) states at a time.

  1. installing
  2. waiting
  3. active

These lifecycles ensure that only one service worker is active at a given time, serving a page. The downside to this is, users currently surfing your newly updated applications would get to experience the new features unless the service worker active at that moment is terminated. This will happen either by the user redirecting to another page entirely and then coming back or closing the browser.

Service worker deep dive.

INSTANTIATING A NEW SERVICE WORKER

const serviceWorkerRegistration = navigator.serviceWorker.register(
"/sw.js",
{updateViaCache: "none"}
);
const serviceWorker =
serviceWorkerRegistration.installing ||
serviceWorkerRegistration.waiting||
serviceWorkerRegistration.active;
navigator.serviceWorker.addEventListener("controllerchange", () => { serviceWorker = navigator.serviceWorker.controller; sendServiceWorkerMsg({ping:"Hi, there I am a page"})const sendServiceWorkerMsg = (msg, target) => { if (target) { target.postMessage(msg); } else if (serviceWorker) { serviceWorker.postMessage(msg); } else { navigator.serviceWorker.postMessage(msg);}}

creating a service worker is a lot different from creating a web worker. For a web worker, all we had to do was to leverage the web worker API using the web worker function constructor and pass in the worker file. With a Service worker, things are a lot more complicated. The web worker acted as a dedicated service hence communication was absolutely straightforward.

A Service worker has mandatory lifecycles that must occur for a service worker to take control of a page or application. One major drawback to this lifecycle is the fact that a user may be running an outdated version of your application because the new service worker hasn’t be activated. hence the user will have to redirect to another page, then back to the page where it was to see the new changes.

COMMUNICATION BETWEEN PAGES AND WEB WORKERS

navigator.serviceWorker.addEventListener("message",(event) => {      console.log(data.msgFromServiceWorker); // {pong: "hi there fr"}
console.log(data.ports)
});

Service workers communicate with pages via a message channel which is created by a service worker per window client. the message channel consists of two ports, the second port is used by the service worker to send messages to the page while it receives information from the first port.

const sendMessage  = (msg) => {   var allClients = await clients.matchAll({ includeUncontrolled:    true });   console.log("all the connected clients", allClients);   return Promise.all( allClients.map(client => {   const newChannel = new MessageChannel();   newChannel.port1.onmessage = serviceWorkerMessageHandler;    return client.postMessage(msg, [newChannel.port2]);}));
}

The allClients variable targets all the tabs or windows that the service worker is currently controlling/active. This is unlike the web worker which just had one dedicated tab for one web worker.

From the above, a message channel is created by the service worker for communication between the page and the service worker via the ports created.

CACHE API

The cache in the browser interfaces network requests, meaning when resources/assets are required by network requests in our applications, the browser checks the cache to find the requested resource. If it exists, it loads it from the cache. If not, it will start its conventional network request trip through the vast internet mass to find it. think of the cache storage like a key-value store like an object in javascript. For cache, the keys are the URL pathnames and values are the response objects. speaking of response objects, it very important to note that these response objects can only be used once. Any further usage would require a clone. This is usually achieved by calling the clone method on the response object from an ajax response.

case in point below:

const cacheAbleUrls = ["/styles.css","/index.html","about.html","/index.js","/dashboard.js"];const saveToCache = async (refresh = false) => {  let cache = await caches.open("some-random-cache-name");  let res;  if (!refresh) {      return Promise.all(       cacheAbleUrls.match(async url => {       res = await cache.match(url);       return res;       })     );  }   res = await fetch(url, {       method: "GET",       credentials: "omit",        cache: "no-cache",    });   if (res.ok) {   await cache.put(url, res);    }  };

As can be seen above, the function saveToCache takes a boolean argument which allows the functionality of toggling the caching action. This would cater for cases where you would want a fresh load to the cache i.e a new update to the styles or scripts.

REFERENCES

[1]https://caniuse.com/#search=web%20workers

[2] https://developer.mozilla.org/en-US/docs/Web/API/Worker

[3] https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

[4] https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm

[5]https://medium.com/@SitePen/the-return-of-sharedarraybuffers-and-atomics-a254032e00a6

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response