Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Worker threads

A thread is a separate line of work inside one program. Your Node.js code runs in one thread, and a long computation blocks the event loop: the server stops answering. The node:worker_threads module runs code in an additional thread — a worker. It computes in parallel while the main thread keeps serving requests.

A worker in one file

A file can be both the main program and the worker at once — the isMainThread flag tells them apart:

import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
 
function countWords(text) {
  return text.split(/\s+/).filter(Boolean).length;
}
 
if (isMainThread) {
  const worker = new Worker(new URL(import.meta.url), {
    workerData: 'Hello from the blog main thread',
  });
  worker.on('message', (count) => console.log(`Words: ${count}`));
} else {
  parentPort.postMessage(countWords(workerData));
}

It prints "Words: 6". The main thread creates a worker from the same file (import.meta.url is its address) and passes data in workerData. The worker computes and sends the result with parentPort.postMessage().

How they communicate

Threads share no variables. They exchange messages, and the data is copied:

WhereWhat to use
Main thread → worker at creationworkerData
Worker → main threadparentPort.postMessage(data)
Main thread receivesworker.on('message', fn)
An error in the workerworker.on('error', fn)

A separate worker file

In a project, a worker usually has its own file. Create src/workers/stats.js — it computes statistics over all posts:

// src/workers/stats.js
import { parentPort, workerData } from 'node:worker_threads';
 
const words = workerData.posts
  .map((post) => post.body.split(/\s+/).filter(Boolean).length)
  .reduce((sum, n) => sum + n, 0);
 
parentPort.postMessage({ posts: workerData.posts.length, words });

The main thread wraps the start in a promise to await the result:

// src/stats.js
import { Worker } from 'node:worker_threads';
 
export function calculateStats(posts) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(new URL('./workers/stats.js', import.meta.url), {
      workerData: { posts },
    });
    worker.once('message', resolve);
    worker.once('error', reject);
  });
}

In the server's /stats route call await calculateStats(await loadPosts()) — the counting runs in a separate thread, and other visitors notice no delay.

When a worker is needed

Workers are for CPU work: compressing images, parsing huge JSON, complex calculations. They are not needed for reading files or network requests — those do not block the event loop anyway.

Common mistake

A worker receives a copy of the data, not a reference to it. Change an object inside the worker and it stays the same in the main thread:

// src/workers/stats.js
workerData.posts.push({ title: 'New' }); // the main thread will not see this

Always return the result as a message with parentPort.postMessage().

Thanks for reading!

Donate to the site — we work hard for you.