Multithreading in JavaScript

Auteur(s) de l'article

By nature, JavaScript is a single-thread language. It has been designed to run on the client side and did not need the multithreading capacities of the server-based languages. But today, we ask more and more complex and heavy operations from JavaScript and are limited by this side of the language. To overcome this shortcut, Web Workers are coming to the rescue.
JavaScript running on a single-thread means that only one line of code at a time can be executed, one after the other - only one Call Stack exists at a time).
With asynchronous functions, we can get around some of the limitations of single-threading. But still, only one context can exist at a time.
In order to do the labor-intensive work that is sometimes required from JavaScript nowadays, Web Workers have been created. By delegating some tasks to the background, it mimics the multi-threading abilities.
Note that JavaScript is single-threaded by nature, so at a given instant, only one task will be executing, although control can shift between different promises, making execution of the promises appear concurrent. Parallel execution in JavaScript can only be achieved through worker threads.

MDN docs

Running parallel processes

Web Workers are JavaScript files that runs in the background and communicates with the main thread via messages. They can transfer data and process process-intensive operations. Because they operate on different threads, they prevent blocked UI from happening.
Multiple workers can be instantiated, each creating its own isolated context and communicating directly with its creator. This means we simulate multi-threading by creating multiple contexts and Call Stacks which run in parallel and communicate with the main thread.

How does it works concretely ?

Let's say we want to iterate through a class of students and register them if they are not already registered.
<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Registered students</title>
</head>
<body>
  <ul id="classRoom"></ul>
  <script src="./main.js"></script>
</body>
</html>
// main.js

// An array of objects, containing the information about each students
const students = [
  {
    name: 'Hermione',
    isRegistered: true,
  },
  {
    name: 'Harry',
    isRegistered: false,
  },
  {
    name: 'Ron',
    isRegistered: true,
  },
  {
    name: 'Ginny',
    isRegistered: true,
  },
  {
    name: 'Fred',
    isRegistered: false,
  },
];

const classRoom = document.getElementById('classRoom');

students.forEach(student => {
  const listItem = document.createElement('li');
  listItem.innerText = student.name;

  if (!student.isRegistered) {
    // Do the registration in a service worker
    const worker = new Worker('./doRegistration.js');
    
    // We communicate the student information to the worker
    worker.postMessage(student);

    // We listen to the worker to know about the registration process
    worker.addEventListener('message', e => {
      if (e.data) {
        // When the registration is done, we add the student to the class room
        classRoom.appendChild(listItem);
      }
    });
  } else {
    // We directly add the student to the class room
    classRoom.appendChild(listItem);
  }
  
});
// doRegistration.js

self.addEventListener('message', e => {
  const student = e.data;

  // Heavy computation: registering takes time
  let k = 0
  const limit = 3000000000;
  while (k < limit) {
    k++
  }

  student.isRegistered = true;
  // We sent back the result to the main thread
  postMessage(true);
  close();
});
Here is the Codepen:

Transferable objets

To communicate between the main thread and the Web Worker, we use the postMessage() function, passing the data we need. One thing worth noting is the data is copied, which can create overhead if it is rather large or complex (for example a heavy file).
To prevent the loss in performance, we can use Transferable Objects. This way, data are not copied, but transferred from one context to another.
However, this means that the data is no longer available in the previous context once it is transferred to the new context.

Worker environment

Despite how effective it can be, Web Workers have a few limitations. Mainly, they only have access to a subset of JavaScript features and cannot directly access and manipule the DOM.
Another good point to keep in mind is that Web Workers can create subworkers. However, be mindful: there is no limitations to how many Web Workers can run in parallel, until the user's system resources is fully consumed.