Concurrency and Multithreading in JavaScript: From A to Z
JavaScript is historically a single-threaded language operating within the paradigm of event-driven programming. However, modern JavaScript (especially in the Node.js environment) offers powerful tools for working with concurrency and multithreading. In this article, we will break down how Event Loop, Promises, async/await, Worker Threads, and clustering work, and how to use these mechanisms to improve the performance of your applications.
1. Event Loop: The Foundation of Concurrency in JavaScript
The Event Loop is the heart of JavaScript's asynchronous model. It allows handling multiple operations without blocking the main execution thread. In Node.js, the Event Loop manages task queues (macrotasks and microtasks), ensuring non-blocking I/O.
Example of Event Loop operation:
console.log('Start');
setTimeout(() => { console.log('Timer 1');}, 0);
process.nextTick(() => { console.log('nextTick');});
Promise.resolve().then(() => { console.log('Promise');});
console.log('End');
// Output:// Start// End// nextTick// Promise// Timer 1It is important to understand the execution order: synchronous code → microtasks (process.nextTick, Promise.then) → macrotasks (setTimeout, setInterval).
2. Promises and async/await: Managing Asynchrony
Promises are objects representing the result of an asynchronous operation. They help avoid "callback hell" and allow building call chains. async/await is syntactic sugar over Promises, making asynchronous code look synchronous.
Example with async/await:
const fetchData = async (url) => { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } return await response.json();};
const main = async () => { try { const data = await fetchData('https://api.example.com/data'); console.log('Received data:', data); } catch (error) { console.error('Error:', error.message); }};
main();This approach allows executing multiple asynchronous operations in parallel using Promise.all():
const [user, posts] = await Promise.all([ fetchUser(userId), fetchPosts(userId)]);console.log(user, posts);3. Worker Threads: True Multithreading in Node.js
Starting from Node.js 10.5.0, the worker_threads module provides the ability to create real threads for CPU-intensive tasks. Each Worker runs in a separate thread with its own V8 instance.
Example of creating a Worker:
// main.jsconst { Worker } = require('worker_threads');
const runWorker = (workerData) => { return new Promise((resolve, reject) => { const worker = new Worker('./worker.js', { workerData });
worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) { reject(new Error(`Worker stopped with code ${code}`)); } }); });};
const main = async () => { const result = await runWorker({ iterations: 1000000 }); console.log('Result:', result);};
main();// worker.jsconst { parentPort, workerData } = require('worker_threads');
const heavyComputation = (iterations) => { let result = 0; for (let i = 0; i < iterations; i++) { result += Math.sqrt(i); } return result;};
const result = heavyComputation(workerData.iterations);parentPort.postMessage(result);Worker Threads are ideal for tasks that block the Event Loop: image processing, mathematical calculations, parsing large files.