Web Workers API: Multithreading in JavaScript

·

4 min read

If you have been using JavaScript, you may know, that it is a single-threaded scripting language that works within HTML files. This means only one statement is executed at a time. However, we are also aware that JavaScript has asynchronous behavior with which we can achieve concurrency. But, Asynchronous is not multithreading, as it is also dependent on a single thread. To some extent, even asynchronous operations block the DOM rendering on the browser.

Multithreading enables processing of multiple threads at once, rather than multiple processes. By incorporating multithreading, programs can perform multiple operations at once.

So, How can we achieve multithreading in JavaScript? Answer — Web Workers! In JavaScript, you can utilize parallel programming to perform multiple operations simultaneously using web workers.

One way to think of web workers, they’re pieces of code, which are process-intensive tasks that take a lot of time to run. We transfer this expensive logic to the browser to execute in the background (separate thread). Since it runs in the background, it doesn’t affect the JavaScript in the main thread responsible for the web page.

Web workers are JavaScript code that execute in a separate thread with no direct access to the DOM.

Web workers aren’t completely isolated from the main thread, though. When a worker needs to communicate with the main thread, it can send a message and the main thread can send a message on its own as a response.

How to use Web Workers in JavaScript

For creating a new worker we use Worker() constructor, specifying the URI of a script to execute in the worker thread in the main.js file.

The worker code must be in a separate file, here in this case worker.js, which we can use to create a separate worker thread by using the Worker constructor and passing it to the path of the file.

// main.js
if (window.Worker) {
   const myWorker = new Worker('worker.js');
} else {
   console.log('Your browser doesn't support web workers');
}

Sending and receiving messages from a dedicated worker can be achieved by postMessage() method and onmessage event handler. The data type of the message can be a string, number, boolean, array, object, null or undefined. Data is always passed by value and serialized then de-serialized during the communication process.

You can only pass one argument to postMessage. If you wish to pass multiple arguments use an object instead.

// main.js
// To receive the message from worker
myWorker.onmessage = (event) => {
  var message = event.data;
  ...
};

OR 

myWorker.addEventListener('message', function(event) {
  var message = event.data;
  ...
});

--------

// To post a message to worker
myWorker.postMessage('Posting a new message');

Now, for the worker to hear the message, we will have to set an event listener in the worker.js file for the message event.

// worker.js
// To receive message from main thread
self.addEventListener('message', function(event) {
// message received from main thread
  var message = event.data;
  ...
// To post a message to main thread
  self.postMessage("message received");
});

OR

self.onmessage = (event) => {
  var message = event.data;
  ...
// To post a message to main thread
  self.postMessage("message received");
 };

Any logic errors caused by the data passed by the script can be caught using onerror event handler.

// main.js
myWorker.onerror = function(event) {
  console.log("Error in file: ", event.filename);
  console.log("Error in Line number: ", event.lineno);
  console.log("Error Description: ", event.message);
};

The web worker thread can be stopped using the close() method.

// worker.js
self.addEventListener('message', function(event) {
  var message = event.data;
  self.postMessage('message received');
  self.close();                
// Closing the thread
});

Points to be noted

  1. The data exchanged between the main thread and the workers are copied rather than shared.
  2. You can not update DOM from the worker thread as they run in a separate context from the main window.
  3. Workers don’t have access to window and document object but they do have access to some of the window object's capabilities like Websockets and indexedDB. Also, it can access Location, AppCache
  4. The script must be served from the same host or domain for security reasons, and that is also the reason the web workers won’t work if we open the file locally with a file:// scheme.

Use cases of web workers

Any intense CPU computation that would block the main UI thread could benefit from being offloaded to a Web Worker. A few of the use cases of workers are as follows —

  1. Prefetching and/or caching.
  2. Web I/O - Polling URLs in the background. That way you don’t block the UI waiting for polling results.
  3. Processing large data sets or Updating rows of a local web database (indexedDB).
  4. Canvas drawing, image filtering, and image manipulation.
  5. Code syntax highlighting or real-time text formatting.

Conclusion

Using Web workers, we can achieve parallel processing of multiple operations by freeing the browser’s main thread from heavy computations by running the worker script in the background(separate thread). This is an ideal solution if the application performs expensive logic in the browser, and also helps in reaching a higher performance of the application.