Web Workers and making JavaScript Multi-Threaded

Web Workers and making JavaScript Multi-Threaded

JavaScript is Single-Threaded

JavaScript is a single-threaded programming language, which means it has a single call stack and can only execute one task at a time. When a script is running, it blocks other scripts from running until it completes.

This can lead to performance issues, especially when dealing with long-running or computationally intensive tasks, as they can freeze the user interface and make the application unresponsive.

Example of problematic code

Here's an example of problematic code that can freeze the user interface:

function longRunningTask() {
  let sum = 0;
  for (let i = 0; i < 1000000000; i++) {
    sum += i;
  }
  console.log(sum);
}

longRunningTask();

function otherTask() {
  console.log("This is another task");
}

otherTask();

longRunningTask is a computationally intensive task. For the sake of simplicity, we're just summing numbers in a loop. This could be something different.

The problem we encounter here: We can't execute otherTask until longRunningTask completes. This can cause the user interface to freeze, and the application becomes unresponsive.

Multi-Threaded Execution with Web Workers

Web Workers API allows you to run JavaScript code in the background, in a separate thread. You can run multiple scripts concurrently. This is called multi-threaded execution. Multi-threaded means that multiple threads can run simultaneously, allowing you to perform multiple tasks at the same time.

Speed up the slow example with Web Workers

Let's speed up the slow example with Web Workers. We'll move the longRunningTask function to a Web Worker, so it doesn't block the main thread:

function longRunningTask() {
  const worker = new Worker("worker.js");
  worker.postMessage("start");
  worker.onmessage = (event) => {
    console.log(event.data);
    worker.terminate();
  };
}

longRunningTask();

function otherTask() {
  console.log("This is another task");
}

otherTask();

In the updated code, we create a worker with new Worker("worker.js"). The worker runs the code in a separate file called worker.js. This could be any file name. The code will run in a separate thread, so it doesn't block the main thread.

worker.postMessage("start") sends a message to the worker to start the task. The worker will perform the task and send the result back to the main thread with worker.onmessage.

This will make more sense when we look at the worker.js file:

self.onmessage = (event) => {
  if (event.data === "start") {
    let sum = 0;
    for (let i = 0; i < 1000000000; i++) {
      sum += i;
    }
    self.postMessage(sum);
  }
};

The message you send with worker.postMessage is received in the worker with self.onmessage. event.data contains the message you sent. In this case, we're checking if the message is "start".

If you wanted to, you could send more complex data to the worker. For example, you could send an object with multiple properties.

When the worker completes the task, it sends the result back to the main thread with self.postMessage. The main thread receives the result with worker.onmessage and calls worker.terminate() to stop the worker.

This lets the main thread continue executing other tasks, like otherTask, without being blocked by longRunningTask.

Downsides

Web Workers are great for offloading heavy tasks to a separate thread, but they come with some downsides:

  • Web Workers can't access the DOM directly. They run in a separate global context, so they can't interact with the main thread's DOM.

  • Web Workers can't access global variables or functions from the main thread. You need to pass data between the main thread and the worker using messages. This increases complexity.

  • Debugging Web Workers can be more challenging compared to regular JavaScript code, as they run in a separate thread with limited access to the browser's developer tools.

Conclusion

If you need to run computationally intensive tasks in the background without blocking the main thread, Web Workers are a great solution. They allow you to run JavaScript code concurrently in separate threads, improving performance and user experience.