Using Web Workers to Speed-Up JavaScript Applications

How to run JavaScript in multiple threads

Bhagya Vithana
Bits and Pieces

--

Web Workers are a method of instructing the browser to run large, time-consuming tasks in the background. Its ability to spawn new threads allows you to prioritize work and address the blocking behavior in single-threaded languages like JavaScript.

But do we really need multi-threaded JavaScript? To answer that, let’s understand where single-threaded JavaScript hit its limits.

Limitation with Single-Threaded JavaScript

Modern web applications demand more from the browsers. Some of these tasks are long-running that can cause the user interface to freeze for few seconds. By all means, it’s bad for the end-user experience.

To give you a better understanding, I have created a sample application to simulate it in the browser. Here the Start_Animation button will move the bicycles forward while the Run_Calculation button calculates the prime number sequence that demands more CPU.

After you run the above code, you will see an output like below. If you look closely, you will notice a frozen animation for few seconds when you call the Run_Calculation function.

Demo Application without a Web Worker

Since you have observed the limitations with single-threaded JavaScript, let’s see how we can use Web Workers to overcome this.

Tip: Build with independent components, for speed and scale

Instead of building monolithic apps, build independent components first and compose them into features and applications. It makes development faster and helps teams build more consistent and scalable applications.

OSS Tools like Bit offer a great developer experience for building independent components and composing applications. Many teams start by building their Design Systems or Micro Frontends, through independent components.
Give it a try →

An independently source-controlled and shared “card” component. On the right => its dependency graph, auto-generated by Bit.

Integrating Web Worker With JavaScript

Let’s see how we can use a Web Worker to optimize the above code.

Step 1 — Split your functions.

There are 2 main functions in the previous example named as Run_Calculation and Start_Animation. Since running both these functions in the same thread caused one function to freeze, we need to separate these 2 and run them in separate threads.

So, I’ve created a new file called worker.js and moved the prime number generation part there.

It is essential to use a separate JS file for the Web Workers. Since main script and web worker script must be fully independent to operate on different threads.

If not, you won’t be able to keep the execution context purely threadsafe, and it can cause unexpected errors in your parallel execution.

In general, a web worker is created in the main thread. So, you need to call the Worker() constructor, specifying the script’s location to execute in the worker thread.

I’m sure you must have noticed some modifications other than the earlier discussed functions. Don’t worry, I will explain them in the next step.

Step 2 — Implement communication between 2 files.

Now, we have 2 separate scripts, and we need to implement a way to communicate between these two. For that, you need to understand how to pass data between your main thread and the web worker thread.

The Web Worker API has a postMessage() method for sending messages and an onmessage event handler to receive messages.

If we consider a situation where we need to pass from the web worker thread to the main thread, we can use postMessage() function within the worker file, and the worker.onmessage listener inside the main thread to listen to messages from the worker.

Also, these same functions are used to pass data from the main thread to the web worker thread as well. The only difference we need to make is calling worker.postMessage() on the main thread and onmessage on the worker thread.

Tip: It is important to keep in mind that, onmessage and postMessage() need to be hung off the worker object when used in the main thread, but not within the worker thread. This will improve the effectiveness of the worker while ensuring a safe cross-origin communication

As you noticed, I have already modified both files using onmessage event handler and postMessage() function to establish communication between the main thread and web worker thread.

//worker.js file
self.onmessage = event => {
const num = event.data;
const result = prime(num);
self.postMessage(result);
self.close();
};
//main_scrpit.js
let worker = new Worker(“./worker.js”);
const Run_Calculation = () => {
const num = 50000;
worker.postMessage(num);
};
worker.onmessage = event => {
const num = event.data;
};

Note: When a message is passed between the main thread and worker, it will be completely moved. Not shared.

Now, it is all set, and you can reload the program in your browser, start the animation, and press the Run_Calculation button to start the calculation. You’ll notice that the prime number calculation result is still listed in the browser console, but this has no impact on the movement of the icons. So, We can see that our application’s performance has greatly improved.

Demo Application With a Web Worker

Step 3 — Terminate the web worker.

As a practice, it is better to terminate a worker after it has fulfilled its function since it frees up resources for other applications. You can wither use terminate() function or close() function for this.

However, it is essential to know when to choose between terminate () and close() methods since they serve different purposes.

The close() function is only visible within the worker files. Hence this function can only be used within the worker file to terminate the worker. On the other hand, the terminate() method is a part of the worker object’s interface, and you can call it from the main JavaScript file.

// immediately terminate the main JS file
worker.terminate();
// stop the worker from the worker code.
self.close();

You can find the full demo project in my GitHub account.

Final Thoughts

Web Workers are a simple way of spawning new threads to speed up your application. In my example, I used a computationally heavy task to show the difference.

In addition to that, there are many use cases of Web Workers like caching of data and web pages, background I/O operations, analyzing video or audio data, performing periodic tasks, etc.

Most importantly, you don’t have to limit yourself to a single web worker as well. You can spawn as many threads as you want and run tasks parallelly in them as you wish. But I always advise you not to create Web Workers and spawn additional threads without any use.

In this article, I’ve touched on what I feel are the most important points you need to know about multi-threaded executions with Web Workers.

Once you understand how Web Workers work, it will be easy to determine how to use them in your project. So, I invite you to get a hands-on experience with Web Workers and share your thoughts in the comments section.

Thank you for Reading !!!

--

--

Software Engineer| Technical Writer| University of Moratuwa| Faculty of Information Technology