Web Workers in JavaScript

Understanding Web Worker API in JavaScript

Build Solutions
5 min readAug 26, 2022
Background image showing the topic JavaScript web Worker

Web workers are used to offload heavy computational tasks from the JavaScript main thread in other to avoid blocking the main thread. This helps eliminate bad user experience as the user will be able to use the browser while the task is being handle in the background, sounds good right, something even better is the fact that Web Worker is an old API and is available in all major browsers.

Web workers makes JavaScript partially multi-threaded and not exactly multi-threaded, there is a misconception that says web workers make JavaScript multi-threaded, but in reality JavaScript uses workers to handle events like it’s multi-threaded. The worker will have to send the result back to the main thread as a message or events, because it does not access to the DOM and most window objects. The reason you are not allowed to make changes to the DOM from a different thread is because it instills bad coding practices and will lead to writing code which will slow the UI.

“It should be noted that it is very easy to make your program slower using mutexes and locks (the common “why is my multithreaded program slower” problem). The way to make shared memory programs fast is to structure your code so that you rarely touch write locks. Unfortunately, GUI programs tend to modify the screen a lot. Which means that making the UI subsystem multi-threaded will tend to make the program much slower than a single-threaded program. Message passing forces programmers to discipline themselves to not write to the DOM from multiple threads.” – slebetman

The web worker uses the self (the worker global scope) object instead of the window object as the global scope. The worker global scope also uses ‘addEventListener’ to listen for events or the “self.onmessage” or just “onmessage”. The supported web APIs for web workers can be seen here.

There are 3 types of workers

  1. Dedicated workers: are workers that are used by a single script.
  2. Shared workers: are used by multiple scripts, the worker uses the “onconnect” method, to communicate through ports.
  3. Service Workers: “essentially act as proxy servers that sit between web applications, the browser, and the network (when available). They are intended, among other things, to enable the creation of effective offline experiences, intercept network requests and take appropriate action based on whether the network is available, and update assets residing on the server. They will also allow access to push notifications and background sync APIs” — Mozilla

Example of How Web Workers are Used

This will be a simple counter app that has a section that does a complex type of calculation that is CPU intensive and would block the main thread for sometime and prevent the UI from responding.

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Web Worker Tutorial</title></head><body><h1>Web Worker Demo</h1><button id="increment-btn">Increment</button><span id="counter-display">0</span><button id="decrement-btn">Decrement</button><h3>Maximize Number!</h3><div><form id="number-form" name="number-form"><input type="number" id="number-input" name="number-input" style="display: block; margin-bottom: 20px;"><button type="submit">Compound</button></form></div><script src="app.js"></script></body></html>

The above code is just the HTML containing a form with ‘number input’ to when submitted will be compounded. The other is a simple counter, it increments by 1 and decrements by 1.

let displayValue = 0;document.getElementById('increment-btn').addEventListener('click', () => {displayValue = ++displayValue;document.getElementById('counter-display').textContent = displayValue;})document.getElementById('decrement-btn').addEventListener('click', () => {displayValue = --displayValue;document.getElementById('counter-display').textContent = displayValue;})document.getElementById('number-form').addEventListener('submit', (event) => {event.preventDefault();const numberToCompute = document.forms['number-form']['number-input'].value;const compoundedDisplay = document.getElementById('compounded-display');let addition = 0;for (let i = 0; i < numberToCompute; i++) {addition += i;}compoundedDisplay.textContent = addition;})

The JavaScript file simply takes the values from the form and an event Listener is added to the buttons to either increment or decrement the counter.

This should work very well if we are working with small digits but if we start compounding in billions it will freeze the UI and will lead to restricted access and will prevent the counter from working.

To solve this we’d need to use web workers, this will create a second thread where a section of the code will be run without interrupting the main thread.

The access the worker we’d need to instantiate the worker class and pass the worker file, the file should be located in the same folder as the JS file. The for loop will be separated from the main JS file and moved to the worker JS file. The new main JS file should look like this

let displayValue = 0;document.getElementById('increment-btn').addEventListener('click', () => {displayValue = ++displayValue;document.getElementById('counter-display').textContent = displayValue;})document.getElementById('decrement-btn').addEventListener('click', () => {displayValue = --displayValue;document.getElementById('counter-display').textContent = displayValue;})document.getElementById('number-form').addEventListener('submit', (event) => {event.preventDefault();const numberToCompute = document.forms['number-form']['number-input'].value;const compoundedDisplay = document.getElementById('compounded-display');const worker = new Worker('worker.js');worker.addEventListener('message', (message) => {compoundedDisplay.textContent = message.data;})worker.postMessage({data: numberToCompute});})

I simply created and instance of the worker, passed the variable containing the value of the input from the form, using the postMessage method. And an event listener was added to the worker to be able to listen for the message from the worker to know when it is done computing. Notice that I passed an object to the worker, because if I passed a number it would throw an error, strings can be passed as well. The worker file should look like this;

addEventListener('message', (message) => {const numberToCompute = message.data.data;console.log(message.data.data)let addition = 0;for (let i = 0; i < numberToCompute; i++) {addition += i;}postMessage(addition);})

This is similar to the what was done in the main JS file, after calculating the result is sent to the main thread using the post message. Like I said earlier the self global object isn’t necessary in the worker file. Now if we should run the code we’d notice that there is no freezing of the UI and the main thread is completely free and our counter works. Another thing to know is that you can control the worker from the main thread, assuming you wanted to end a worker process bacause you no longer needed the information, you can simply run ``` worker.terminate(); ``` it will terminate the running worker.

I hope you were able to learn something new and you understand workers better now. Web worker is a very old JavaScript API but isn’t really common.

Happy Coding!

--

--

Build Solutions

I am a Software Engineer, Digital Marketer, I write about things in my sphere to help people who are yet to get the experiences I've had on my journey.