Web Workers in Javascript

Web Workers in Javascript

JavaScript is commonly referred to as a single-threaded language. This primarily pertains to the main thread, where the browser executes the majority of tasks visible to users. These tasks encompass scripting, certain rendering processes, parsing HTML and CSS, as well as other user-centric activities that shape the overall browsing experience.

Despite this limitation, Javascript code is non-blocking and snappy thanks to an event loop-based architecture. I promise web workers will make much more sense once we have the basics covered so let's quickly review this diagram.

Imagine a user clicking on a button with an Associated event handler the Handler function will be pushed into the Callback queue first the event Loop is always monitoring this queue whenever the stack is empty tasks are moved from the queue to the stack and code is executed any object needed during this execution will be allocated in the heap this is a sync callback-based model that is what allows your code to run smoothly despite some long-running tasks like working with a network in practice whenever a call to the server is performed the request is dispatched to the fetch API and the event Loop clears the stack when the response is available a callback is pushed in the queue and will end up being processed when its turn comes.

for (let i = 0; i < 1000000000; i++) {
      // Do something, for example, perform a simple calculation
      const result = Math.sqrt(i);
}

So this model works but you can still run into scenarios when the UI thread becomes blocked imagine for instance, placing a function like this one on the stack and calculating the sum will take a few seconds even on powerful devices while the Loop is executed the function will stay on the stack and all other tasks will wait in the queue granted and hence UI will be non-responsive until the Loop is completed.

It's not that often that you need to sum up billions of numbers however the reality of today is that modern front-end development interacts with AI libraries and complex web assembly algorithms so handling heavy intensive workloads might become a pretty common requirement shortly.

This is where web workers can help since they run scripts in the background on separate threads compared to the main thread. So, let's take a look at how we can implement web workers.

In your main JavaScript file:

// Create a new web worker
const myWorker = new Worker('worker.js');

// Define a function to handle messages from the worker
myWorker.onmessage = function(event) {
  console.log('Message received from worker:', event.data);
};

// Define a function to send a message to the worker
function sendMessageToWorker() {
  myWorker.postMessage('start');
}

And in your worker.js file:

// Define a function to handle messages from the main thread
onmessage = function(event) {
  console.log('Message received from main thread:', event.data);

  if (event.data === 'start') {
    // Perform a computationally intensive task (running a loop a billion times)
    const startTime = performance.now();
    for (let i = 0; i < 1000000000; i++) {
      // Do something, for example, perform a simple calculation
      const result = Math.sqrt(i);
    }
    const endTime = performance.now();
    const duration = endTime - startTime;

    // Send the duration back to the main thread
    postMessage(duration);
  }
};

When you call the sendMessageToWorker() function from your main JavaScript file, it sends a message to the web worker to initiate the computation. The web worker then executes the computationally intensive task, such as running a for loop a billion times, calculates the task's duration, and sends the duration back to the main thread, where it is logged to the console.

Under the hood, when the getSquareRoot() the function is called, a simple start message is sent to the Web Workers API. Subsequently, the stack is cleared, allowing the event loop to continue moving more work from the queue to the stack. Once the worker computation is complete, the response is sent via callback to the queue. Eventually, it returns to the stack, where the UI is updated, seamlessly avoiding any blocking.

One distinction between the main thread and the worker thread is their ability to manipulate the DOM. Only the main thread can access and manipulate the DOM.

Now, let's consider when to use web workers:

  • CPU-Intensive Tasks

  • Parallel Processing

  • Background Operations

  • Offloading UI Thread

  • Improved Responsiveness

However, there are some drawbacks to using web workers:

  • Browser Support: Older browsers may lack full support for web workers.

  • Communication Overhead: Passing messages incurs serialization/deserialization overhead, especially for large data sets.

  • No DOM Access: Web workers cannot directly access the DOM, limiting their use in UI-related tasks.

  • Additional Complexity: Implementing web workers adds complexity to the codebase, necessitating management of message passing and synchronization.

Thank you for reading. Happy Coding! :)