Web Workers in a Nutshell
June 22, 2022
Web Workers are all about keeping a separate thread on the browser different than the main thread which runs all the rendering efforts of the UI.
As Web workers are somehow tools which web developers are not so into, not very detailed articles around existing and cannot see detailed discoveries around the web, I thought that it would be nice to dive into the concept and give detailed information as much as I can. So another non-Hype guide for you here. Hope you will like it.
There are some advantages of doing it which I will tell more further in the article, for sure. But before that, let’s dive into the conceptual part of what they actually do well.
Web Workers are basically running things in the background within another thread(s) other than the main thread (where browser gets executes the whole) of the browser itself.
They’re for avoiding long-running processes which may block the main thread and slowen the application and UI operations. So it may cause some flagging UI also from the performance perspective, too.
Web Workers are pretty common among browsers and feel free to implement them in your next project once needed. All you need in detail with examples can be also found over here within the specification.
Before moving forward, let’s clarify what threads are. Threads are mostly about running code in different scopes or contexts (whatever else you can go more abstract) simultaneously.
Threads on the browser are actually tiny executional contexts (or a child process / worker threads in Node.js — they’re actually also existing the same reason / goal which offers to developers to allot the load into different child processes especially within infrastructures with multiple-core CPUs for horizontally-scaling purposes without getting blocked under heavy loads as it is single-threaded by nature)
As Web Workers are in the official Web API specification, we can also see the family tree of the scopes for a web worker are getting inherited like so:
self = like window object of the main thread
self has one method: postMessage which sends a data payload in between the main thread and the other web worker threads. And that method uses an algorithm called the Structured Clone Algorithm. That logic is also used to provide functionality to IndexedDB
Beside that, you can mainly import scripts within a web worker while you’re rendering the UI without having any interruption in the processing the script or problem in the UI side.
Web Workers have 2 (two) types based on how they’re chosen based on their capabilities: Dedicated & Shared Workers.
A worker which is only accessible by the code invoking it.
You can see an example here:
The code running on the browser threadThe code waits for a message from the main thread
A worker which is accessible by multiple code (blocks or pieces) or scripts getting used within the main thread. For example, you have different utility functions which does some conversions / calculations on the fly and you’re keeping them in different files within the same project structure.
Once you create a SharedWorker instance within each specific file, you can start using the worker as a shared worker through SharedWorker class.
A “main - worker” thread interaction for having the modulo of a number
That means the worker is also available to be consumed by other contexts like a new tab in a new browser window and iframes and workers.
You can see another & more extensive example here:
Code for sending values to get the square of it from a web workerWe’re multiplying the numbers on the worker threadThe point is to use Web Workers for its own purpose: data calculating / transitioning without keeping the browser rendering busy
Yes, you can. Even alongside many others. There are several & wide range of support on some browser APIs like the following you can directly use within a Web Worker.
- XMLHttpRequest, String, Array, Date & Math, setTimeout, setInterval
- Even fetch, Promise, WebGL and WebSocket
- EventSource which actually composes today’s Server-sent Events interface.
You can find the whole list here.
- If you do not wanna pollute the main thread with some other actions which can be done concurrently in the background, web workers can easily do some transformations requiring some heavy computation power. For example, as a processing a video getting fetched over the network, worker would be the best place to do it. For audio processing, there is an actual worker type: AudioWorklet.
- You can do whatever you want in terms of communicating through a remote resource like an API, etc. by offloading the effort to another thread but only can get performant results once you wanna do some transformations on the data. Seems like using a fetch with WebWorkers does not so effective at all.
- Best performance can be gained by also porting WebAssembly over workers to get near-native or native experience.
- The state management can also be delegated to a web worker in case there is a need to sync and | or cache the data in between client and the remote.
- Once there are multiple independent piece of code or apps (eg. microfrontends) working in the same context of a browser, it possible to offload of loading every individual component / app / microfrontend within a web worker’s context.
- You can provide a transmission layer of events through worker(s) with a publish / subscribe pattern (eg. Client Thread ←→ data ←→ Worker Thread).
- WorkerGlobalScope which is inherited by DedicatedWorkerGlobalScope also has caches property (like how ServiceWorkers have) returning CacheStorage that can give some caching capabilities back like caching static assets and capabilities like caching interaction in between remote resources like edge serves or CDNs.
There are some package and libraries for experimentation across the web. One of the is from developit called workerize which you can give the responsibility of a code block or a function by transferring the blob data of the code itself into worker’s very own context:
microbundle takes your modules and bundles them into common js and | or ESM format on the fly (actually within a worker scope).
greenlet is a library which takes the code and runs in a web worker as async for taking all async operations into a separate worker thread.
A library called partytown is moving operations of loading all 3rd-party scripts into a web worker for offloading the main thread in script-heavy web apps/sites.
And that’s how it does over an example of Google Tag Manager script:
No more two different files / blocks.
web-worker as a universal module to generalize the WebWorker API into both: client and server for compatibility purposes.
One of my favorites here: comlink. This library is for eliminating postMessage interaction from the surface of the implementation and handles it for you in the background with a nice API:
More readable code with comlink for Web Workers
Even, you can go with hooks style for defining workers with web-worker-hooks:
Hooks for the universe!
You can also find any other web workers-related projects here as a Github topic.
You can write down chrome://inspect/#workers on Chrome, click on inspect among the listed workers. Then it opens another DevTools window with the context of the worker. You can either go jump to Network to see what’s happened / going on or click on Inspect to open a new console for you to start debugging the worker’s code.
You can also watch the values of all here.
It is possible to see the context of the self in the DevTools.
The same can be done via about:debugging#workers on Firefox in the address bar. Then you will find it under Shared Workers section.
Firefox has a better UI for its own configuration :)
- As service workers are mostly responsible for proxying on static assets and background tasks like caching and offline capabilities
- They both run an execution in another thread and cannot access to window and document objects as they’re also not able to interact with DOM.
- A window in browser and | or a page can create multiple web workers, service worker has more to do as it takes the whole responsibility of tabs existing.
- Once you close a tab and if there is a web worker initialised & associated with that tab, the web worker dies in the background. But if a service worker has been initialised and run, it keeps living until it takes too long and destroyed by the browser independent from either you close the tab or not.
- Service worker is a great fit for cases with Broadcast API & Background Sync API as they’re not getting destroyed by browser until a serious memory issue happens, and they keep working in the background. That’s a downside for (especially for legacy) mobile devices, just fyi.
- You can find a nice intro and how Service Workers are working here.
Service Workers to the rescue for caching.
As I am keep investigating and discovering some use cases, most probably, that would be great to see some progress on different types of uses cases implemented via Web Workers more than the ones mentioned above.
In general, there is a tendency for defining workers within the same mental bucket but the reality is not like that. Service Workers are more in the PWA side, Web Workers are more like letting the browser’s UI part which does painting / rendering free. There may also be some opportunities in terms of adapting some real-time use cases like Web Sockets and Server-sent Events through web workers.
But in general, I think that there is a huge opportunity in the field which is still not so common where we see some recent implementations of time consuming tasks to be delegated to web workers and hope that would change in the future unless there will be no any other option to use.
The more web becomes real-time, modularised UI strategies occupy their own place on web and the mindset of how to manage state in the client side transition into more client to remote interaction (not joking, that’s possible!), the more we will see different usage & patterns implemented via Web Workers.
Hope you liked reading and wish to see you in the upcoming reads. Thanks.