Quiz

What are Web Workers and how can they be used to improve performance?

Topics
Web APIsJavaScriptPerformance

TL;DR

Web Workers run JavaScript in a background thread so CPU-heavy work does not block browser input and rendering. Create a worker, exchange messages with postMessage(), handle message and error events, and call terminate() when its owning feature is disposed. Workers cannot access the DOM, and messages normally use structured cloning; transfer large ArrayBuffers when ownership can move instead of copying them.

A worker improves responsiveness, not automatically total execution time. Startup, serialization, copying, and coordination have costs, so profile the real task. Use asynchronous APIs or task chunking instead when the work is mostly waiting for I/O or is too small to justify a worker.

// main.js
const worker = new Worker('worker.js');
worker.postMessage('Hello, worker!');
worker.onmessage = function (event) {
console.log('Message from worker:', event.data);
};
worker.onerror = function (event) {
console.error('Worker failed:', event.message);
};
// Call when the feature no longer needs the worker.
function cleanup() {
worker.terminate();
}
// worker.js
onmessage = function (event) {
console.log('Message from main script:', event.data);
postMessage('Hello, main script!');
};

What are Web Workers and how can they be used to improve performance?

Introduction to Web Workers

Web Workers provide a way to run JavaScript in the background, separate from the main execution thread. This allows for performing tasks like heavy computations or I/O operations without blocking the user interface, leading to a smoother user experience.

Creating a Web Worker

To create a Web Worker, you use the Worker constructor and pass the URL of the JavaScript file that contains the worker code.

const worker = new Worker('worker.js');

Communication between main script and Web Worker

Communication between the main script and the Web Worker is done using the postMessage method and the onmessage event handler.

Main script

// main.js
const worker = new Worker('worker.js');
// Send a message to the worker
worker.postMessage('Hello, worker!');
// Receive messages from the worker
worker.onmessage = function (event) {
console.log('Message from worker:', event.data);
};

Worker script

// worker.js
onmessage = function (event) {
console.log('Message from main script:', event.data);
// Send a message back to the main script
postMessage('Hello, main script!');
};

Use cases for Web Workers

Heavy computations

Web Workers can be used to perform heavy computations without blocking the main thread. For example, processing large datasets or performing complex mathematical operations.

// worker.js
onmessage = function (event) {
const result = heavyComputation(event.data);
postMessage(result);
};
function heavyComputation(data) {
// Perform heavy computation here
return data * 2; // Example computation
}

Data processing

Web Workers can be used to process large amounts of data, such as parsing large JSON files or processing images.

// worker.js
onmessage = function (event) {
const processedData = processData(event.data);
postMessage(processedData);
};
function processData(data) {
// Process data here
return data.map((item) => item * 2); // Example processing
}

Limitations of Web Workers

  • Web Workers do not have access to the DOM, so they cannot directly manipulate the user interface.
  • They have a separate scope from the main script, so you need to pass data back and forth using postMessage.
  • Creating and managing Web Workers can add complexity to your codebase.
  • A message is copied using the structured clone algorithm unless transferable ownership is supplied. Repeatedly copying large data can remove the responsiveness benefit.
  • Errors and Promise rejections inside the worker need explicit reporting and handling.
  • Browser Web Workers and Node.js worker_threads solve similar problems but have different globals and APIs.

Transferring large data

When the main thread no longer needs a buffer, transfer it:

const buffer = new ArrayBuffer(16 * 1024 * 1024);
worker.postMessage({ buffer }, [buffer]);
console.log(buffer.byteLength); // 0: ownership moved to the worker

The transfer avoids copying that buffer, but the sender can no longer use it. Shared memory with SharedArrayBuffer requires careful synchronization and, in browsers, appropriate cross-origin isolation headers.

Lifecycle and cancellation

terminate() stops the worker immediately and does not run a graceful cleanup callback. For cooperative cancellation or cleanup, send a cancellation message and design the worker to stop between chunks, then terminate it as a final fallback. Include a request ID in messages when several jobs can be in flight so late results are not applied to the wrong UI state.

Further reading

Exercises

Check your understanding
Beta
Check your understanding Exercise 1 of 2
Check your understanding Exercise 1 of 2

Which workload is a good candidate for a dedicated Web Worker?