Browser APIsFeatured

JavaScript Web Workers: Background Processing Guide

Master Web Workers in JavaScript for background processing. Learn parallel computing, worker types, and performance optimization.

By JavaScriptDoc Team
web workersjavascriptperformanceparallelbrowser

JavaScript Web Workers: Background Processing Guide

Web Workers enable JavaScript applications to run scripts in background threads, allowing for parallel processing without blocking the main UI thread. This guide covers everything you need to know about Web Workers.

Understanding Web Workers

Web Workers make it possible to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface.

// Main thread
if (typeof Worker !== 'undefined') {
  // Create a new worker
  const myWorker = new Worker('worker.js');

  // Send data to worker
  myWorker.postMessage({ cmd: 'start', msg: 'Hello Worker' });

  // Receive messages from worker
  myWorker.onmessage = function (e) {
    console.log('Message from worker:', e.data);
  };

  // Handle errors
  myWorker.onerror = function (error) {
    console.error('Worker error:', error);
  };
} else {
  console.log('Web Workers not supported');
}

// worker.js
self.onmessage = function (e) {
  const data = e.data;

  switch (data.cmd) {
    case 'start':
      self.postMessage('Worker started: ' + data.msg);
      break;
    case 'stop':
      self.postMessage('Worker stopped');
      self.close(); // Terminates the worker
      break;
  }
};

Types of Web Workers

Dedicated Workers

// dedicated-worker.js
let count = 0;

// Listen for messages
self.addEventListener('message', function (e) {
  const { action, data } = e.data;

  switch (action) {
    case 'increment':
      count += data;
      self.postMessage({ type: 'count', value: count });
      break;

    case 'calculate':
      const result = performHeavyCalculation(data);
      self.postMessage({ type: 'result', value: result });
      break;

    case 'fetch':
      fetchData(data.url).then((result) => {
        self.postMessage({ type: 'fetched', data: result });
      });
      break;
  }
});

function performHeavyCalculation(n) {
  // Simulate heavy computation
  let result = 0;
  for (let i = 0; i < n; i++) {
    result += Math.sqrt(i) * Math.random();
  }
  return result;
}

async function fetchData(url) {
  const response = await fetch(url);
  return response.json();
}

// Main thread usage
class WorkerManager {
  constructor(workerPath) {
    this.worker = new Worker(workerPath);
    this.callbacks = new Map();
    this.taskId = 0;

    this.worker.onmessage = (e) => {
      const { id, type, data } = e.data;
      const callback = this.callbacks.get(id);

      if (callback) {
        callback(data);
        this.callbacks.delete(id);
      }
    };
  }

  postTask(action, data) {
    return new Promise((resolve) => {
      const id = this.taskId++;
      this.callbacks.set(id, resolve);

      this.worker.postMessage({ id, action, data });
    });
  }

  terminate() {
    this.worker.terminate();
  }
}

Shared Workers

// shared-worker.js
const connections = [];

self.addEventListener('connect', function (e) {
  const port = e.ports[0];
  connections.push(port);

  port.addEventListener('message', function (e) {
    const { action, data } = e.data;

    switch (action) {
      case 'broadcast':
        // Send to all connected ports
        connections.forEach((p) => {
          p.postMessage({ type: 'broadcast', data: data });
        });
        break;

      case 'getConnectionCount':
        port.postMessage({
          type: 'connectionCount',
          count: connections.length,
        });
        break;
    }
  });

  port.start(); // Required for addEventListener

  // Handle disconnection
  port.addEventListener('close', function () {
    const index = connections.indexOf(port);
    if (index !== -1) {
      connections.splice(index, 1);
    }
  });
});

// Using shared worker in main thread
if (typeof SharedWorker !== 'undefined') {
  const sharedWorker = new SharedWorker('shared-worker.js');

  sharedWorker.port.start();

  sharedWorker.port.postMessage({
    action: 'broadcast',
    data: 'Hello from tab ' + Date.now(),
  });

  sharedWorker.port.onmessage = function (e) {
    console.log('Received:', e.data);
  };
}

Data Transfer and Communication

Transferable Objects

// Efficient data transfer with transferable objects
class ImageProcessor {
  constructor() {
    this.worker = new Worker('image-processor-worker.js');
  }

  processImage(imageData) {
    return new Promise((resolve) => {
      // Create a copy of the image data
      const buffer = imageData.data.buffer;
      const copy = buffer.slice(0);

      this.worker.onmessage = (e) => {
        resolve(
          new ImageData(
            new Uint8ClampedArray(e.data.buffer),
            e.data.width,
            e.data.height
          )
        );
      };

      // Transfer ownership of the buffer to the worker
      this.worker.postMessage(
        {
          buffer: copy,
          width: imageData.width,
          height: imageData.height,
        },
        [copy]
      ); // Transferable list
    });
  }
}

// image-processor-worker.js
self.onmessage = function (e) {
  const { buffer, width, height } = e.data;
  const data = new Uint8ClampedArray(buffer);

  // Process the image
  for (let i = 0; i < data.length; i += 4) {
    // Example: Convert to grayscale
    const gray = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
    data[i] = gray; // Red
    data[i + 1] = gray; // Green
    data[i + 2] = gray; // Blue
    // Alpha channel unchanged
  }

  // Transfer back
  self.postMessage(
    {
      buffer: data.buffer,
      width,
      height,
    },
    [data.buffer]
  );
};

// SharedArrayBuffer for shared memory (where available)
if (typeof SharedArrayBuffer !== 'undefined') {
  class SharedMemoryWorker {
    constructor(workerCount) {
      this.workerCount = workerCount;
      this.workers = [];

      // Create shared memory
      const sharedBuffer = new SharedArrayBuffer(1024 * 1024); // 1MB
      this.sharedArray = new Float32Array(sharedBuffer);

      // Create workers
      for (let i = 0; i < workerCount; i++) {
        const worker = new Worker('shared-memory-worker.js');
        worker.postMessage({
          cmd: 'init',
          sharedBuffer,
          workerId: i,
          workerCount,
        });
        this.workers.push(worker);
      }
    }

    process(data) {
      // Copy data to shared memory
      this.sharedArray.set(data);

      // Start all workers
      const promises = this.workers.map((worker, index) => {
        return new Promise((resolve) => {
          worker.onmessage = () => resolve();
          worker.postMessage({ cmd: 'process', length: data.length });
        });
      });

      return Promise.all(promises).then(() => {
        // Return processed data
        return Array.from(this.sharedArray.slice(0, data.length));
      });
    }
  }
}

Worker Pools

Implementing a Worker Pool

class WorkerPool {
  constructor(workerScript, poolSize = navigator.hardwareConcurrency || 4) {
    this.workerScript = workerScript;
    this.poolSize = poolSize;
    this.workers = [];
    this.freeWorkers = [];
    this.queue = [];

    this.init();
  }

  init() {
    for (let i = 0; i < this.poolSize; i++) {
      const worker = new Worker(this.workerScript);
      worker.id = i;
      worker.isBusy = false;

      worker.onmessage = (e) => {
        const task = worker.currentTask;
        if (task) {
          task.resolve(e.data);
          worker.currentTask = null;
        }

        worker.isBusy = false;
        this.freeWorkers.push(worker);

        // Process next task in queue
        this.processQueue();
      };

      worker.onerror = (error) => {
        const task = worker.currentTask;
        if (task) {
          task.reject(error);
          worker.currentTask = null;
        }

        worker.isBusy = false;
        this.freeWorkers.push(worker);
        this.processQueue();
      };

      this.workers.push(worker);
      this.freeWorkers.push(worker);
    }
  }

  execute(data) {
    return new Promise((resolve, reject) => {
      const task = { data, resolve, reject };

      if (this.freeWorkers.length > 0) {
        this.assignTask(task);
      } else {
        this.queue.push(task);
      }
    });
  }

  assignTask(task) {
    const worker = this.freeWorkers.pop();
    worker.isBusy = true;
    worker.currentTask = task;
    worker.postMessage(task.data);
  }

  processQueue() {
    if (this.queue.length > 0 && this.freeWorkers.length > 0) {
      const task = this.queue.shift();
      this.assignTask(task);
    }
  }

  terminate() {
    this.workers.forEach((worker) => worker.terminate());
    this.workers = [];
    this.freeWorkers = [];
    this.queue = [];
  }

  getStats() {
    return {
      total: this.workers.length,
      busy: this.workers.filter((w) => w.isBusy).length,
      free: this.freeWorkers.length,
      queued: this.queue.length,
    };
  }
}

// Usage example
const pool = new WorkerPool('calculation-worker.js', 4);

// Process multiple tasks
const tasks = [];
for (let i = 0; i < 100; i++) {
  tasks.push(
    pool.execute({
      operation: 'calculate',
      value: i,
    })
  );
}

Promise.all(tasks).then((results) => {
  console.log('All tasks completed:', results);
  pool.terminate();
});

Practical Use Cases

Image Processing

// image-filter-worker.js
const filters = {
  blur(imageData, radius = 5) {
    const { data, width, height } = imageData;
    const output = new Uint8ClampedArray(data);

    for (let y = 0; y < height; y++) {
      for (let x = 0; x < width; x++) {
        let r = 0,
          g = 0,
          b = 0,
          a = 0;
        let count = 0;

        for (let dy = -radius; dy <= radius; dy++) {
          for (let dx = -radius; dx <= radius; dx++) {
            const ny = y + dy;
            const nx = x + dx;

            if (ny >= 0 && ny < height && nx >= 0 && nx < width) {
              const idx = (ny * width + nx) * 4;
              r += data[idx];
              g += data[idx + 1];
              b += data[idx + 2];
              a += data[idx + 3];
              count++;
            }
          }
        }

        const idx = (y * width + x) * 4;
        output[idx] = r / count;
        output[idx + 1] = g / count;
        output[idx + 2] = b / count;
        output[idx + 3] = a / count;
      }
    }

    return output;
  },

  brightness(imageData, adjustment) {
    const { data } = imageData;
    const output = new Uint8ClampedArray(data);

    for (let i = 0; i < data.length; i += 4) {
      output[i] = Math.min(255, Math.max(0, data[i] + adjustment));
      output[i + 1] = Math.min(255, Math.max(0, data[i + 1] + adjustment));
      output[i + 2] = Math.min(255, Math.max(0, data[i + 2] + adjustment));
    }

    return output;
  },

  contrast(imageData, adjustment) {
    const { data } = imageData;
    const output = new Uint8ClampedArray(data);
    const factor = (259 * (adjustment + 255)) / (255 * (259 - adjustment));

    for (let i = 0; i < data.length; i += 4) {
      output[i] = Math.min(255, Math.max(0, factor * (data[i] - 128) + 128));
      output[i + 1] = Math.min(
        255,
        Math.max(0, factor * (data[i + 1] - 128) + 128)
      );
      output[i + 2] = Math.min(
        255,
        Math.max(0, factor * (data[i + 2] - 128) + 128)
      );
    }

    return output;
  },
};

self.onmessage = function (e) {
  const { filter, imageData, params } = e.data;

  if (filters[filter]) {
    const result = filters[filter](imageData, params);

    self.postMessage(
      {
        filter,
        result: result.buffer,
      },
      [result.buffer]
    );
  }
};

// Main thread image processor
class ImageFilterProcessor {
  constructor() {
    this.worker = new Worker('image-filter-worker.js');
  }

  applyFilter(canvas, filter, params) {
    const ctx = canvas.getContext('2d');
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

    return new Promise((resolve) => {
      this.worker.onmessage = (e) => {
        const { result } = e.data;
        const filteredData = new ImageData(
          new Uint8ClampedArray(result),
          canvas.width,
          canvas.height
        );

        ctx.putImageData(filteredData, 0, 0);
        resolve();
      };

      this.worker.postMessage(
        {
          filter,
          imageData: {
            data: imageData.data.buffer,
            width: canvas.width,
            height: canvas.height,
          },
          params,
        },
        [imageData.data.buffer]
      );
    });
  }
}

Data Processing

// data-processor-worker.js
const processors = {
  sort(data, key, order = 'asc') {
    return data.sort((a, b) => {
      const aVal = a[key];
      const bVal = b[key];

      if (order === 'asc') {
        return aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
      } else {
        return aVal > bVal ? -1 : aVal < bVal ? 1 : 0;
      }
    });
  },

  filter(data, conditions) {
    return data.filter((item) => {
      return conditions.every((condition) => {
        const { field, operator, value } = condition;
        const itemValue = item[field];

        switch (operator) {
          case '=':
            return itemValue === value;
          case '!=':
            return itemValue !== value;
          case '>':
            return itemValue > value;
          case '<':
            return itemValue < value;
          case '>=':
            return itemValue >= value;
          case '<=':
            return itemValue <= value;
          case 'contains':
            return String(itemValue).includes(value);
          case 'startsWith':
            return String(itemValue).startsWith(value);
          case 'endsWith':
            return String(itemValue).endsWith(value);
          default:
            return true;
        }
      });
    });
  },

  aggregate(data, groupBy, aggregations) {
    const groups = {};

    // Group data
    data.forEach((item) => {
      const key = item[groupBy];
      if (!groups[key]) {
        groups[key] = [];
      }
      groups[key].push(item);
    });

    // Apply aggregations
    const results = [];

    for (const [key, group] of Object.entries(groups)) {
      const result = { [groupBy]: key };

      aggregations.forEach((agg) => {
        const { field, operation, alias } = agg;
        const values = group
          .map((item) => item[field])
          .filter((v) => v != null);

        switch (operation) {
          case 'sum':
            result[alias] = values.reduce((sum, val) => sum + val, 0);
            break;
          case 'avg':
            result[alias] =
              values.reduce((sum, val) => sum + val, 0) / values.length;
            break;
          case 'min':
            result[alias] = Math.min(...values);
            break;
          case 'max':
            result[alias] = Math.max(...values);
            break;
          case 'count':
            result[alias] = values.length;
            break;
        }
      });

      results.push(result);
    }

    return results;
  },

  analyze(data) {
    const analysis = {
      totalRecords: data.length,
      fields: {},
      summary: {},
    };

    if (data.length === 0) return analysis;

    // Analyze each field
    const fields = Object.keys(data[0]);

    fields.forEach((field) => {
      const values = data.map((item) => item[field]);
      const fieldAnalysis = {
        type: typeof values[0],
        uniqueValues: new Set(values).size,
        nullCount: values.filter((v) => v == null).length,
      };

      if (fieldAnalysis.type === 'number') {
        const numbers = values.filter((v) => typeof v === 'number');
        fieldAnalysis.min = Math.min(...numbers);
        fieldAnalysis.max = Math.max(...numbers);
        fieldAnalysis.avg =
          numbers.reduce((sum, n) => sum + n, 0) / numbers.length;
        fieldAnalysis.sum = numbers.reduce((sum, n) => sum + n, 0);
      } else if (fieldAnalysis.type === 'string') {
        fieldAnalysis.minLength = Math.min(
          ...values.map((v) => (v ? v.length : 0))
        );
        fieldAnalysis.maxLength = Math.max(
          ...values.map((v) => (v ? v.length : 0))
        );
      }

      analysis.fields[field] = fieldAnalysis;
    });

    return analysis;
  },
};

self.onmessage = function (e) {
  const { operation, data, params } = e.data;

  try {
    const result = processors[operation](data, ...params);
    self.postMessage({ success: true, result });
  } catch (error) {
    self.postMessage({
      success: false,
      error: error.message,
    });
  }
};

Cryptography Operations

// crypto-worker.js
self.onmessage = async function (e) {
  const { operation, data } = e.data;

  try {
    let result;

    switch (operation) {
      case 'generateKeyPair':
        result = await generateKeyPair();
        break;

      case 'encrypt':
        result = await encryptData(data.text, data.publicKey);
        break;

      case 'decrypt':
        result = await decryptData(data.encrypted, data.privateKey);
        break;

      case 'hash':
        result = await hashData(data.text, data.algorithm);
        break;

      case 'sign':
        result = await signData(data.text, data.privateKey);
        break;

      case 'verify':
        result = await verifySignature(
          data.text,
          data.signature,
          data.publicKey
        );
        break;
    }

    self.postMessage({ success: true, result });
  } catch (error) {
    self.postMessage({ success: false, error: error.message });
  }
};

async function generateKeyPair() {
  const keyPair = await crypto.subtle.generateKey(
    {
      name: 'RSA-OAEP',
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-256',
    },
    true,
    ['encrypt', 'decrypt']
  );

  const publicKey = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
  const privateKey = await crypto.subtle.exportKey('jwk', keyPair.privateKey);

  return { publicKey, privateKey };
}

async function encryptData(text, publicKeyJwk) {
  const encoder = new TextEncoder();
  const data = encoder.encode(text);

  const publicKey = await crypto.subtle.importKey(
    'jwk',
    publicKeyJwk,
    {
      name: 'RSA-OAEP',
      hash: 'SHA-256',
    },
    false,
    ['encrypt']
  );

  const encrypted = await crypto.subtle.encrypt(
    { name: 'RSA-OAEP' },
    publicKey,
    data
  );

  return Array.from(new Uint8Array(encrypted));
}

async function decryptData(encryptedArray, privateKeyJwk) {
  const encrypted = new Uint8Array(encryptedArray).buffer;

  const privateKey = await crypto.subtle.importKey(
    'jwk',
    privateKeyJwk,
    {
      name: 'RSA-OAEP',
      hash: 'SHA-256',
    },
    false,
    ['decrypt']
  );

  const decrypted = await crypto.subtle.decrypt(
    { name: 'RSA-OAEP' },
    privateKey,
    encrypted
  );

  const decoder = new TextDecoder();
  return decoder.decode(decrypted);
}

async function hashData(text, algorithm = 'SHA-256') {
  const encoder = new TextEncoder();
  const data = encoder.encode(text);

  const hashBuffer = await crypto.subtle.digest(algorithm, data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));

  return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
}

Performance Optimization

Optimizing Worker Communication

class OptimizedWorkerCommunication {
  constructor(workerPath) {
    this.worker = new Worker(workerPath);
    this.messageQueue = [];
    this.isProcessing = false;
    this.batchSize = 100;
    this.batchTimeout = 16; // One frame

    this.setupMessageHandler();
  }

  setupMessageHandler() {
    this.worker.onmessage = (e) => {
      const { batchId, results } = e.data;
      const batch = this.batches.get(batchId);

      if (batch) {
        batch.resolve(results);
        this.batches.delete(batchId);
      }

      this.isProcessing = false;
      this.processBatch();
    };
  }

  send(data) {
    return new Promise((resolve) => {
      this.messageQueue.push({ data, resolve });

      if (!this.batchTimer) {
        this.batchTimer = setTimeout(() => {
          this.processBatch();
        }, this.batchTimeout);
      }
    });
  }

  processBatch() {
    if (this.isProcessing || this.messageQueue.length === 0) {
      return;
    }

    clearTimeout(this.batchTimer);
    this.batchTimer = null;

    const batch = this.messageQueue.splice(0, this.batchSize);
    const batchId = Date.now() + Math.random();

    this.batches = this.batches || new Map();
    this.batches.set(batchId, {
      resolve: (results) => {
        results.forEach((result, index) => {
          batch[index].resolve(result);
        });
      },
    });

    this.isProcessing = true;
    this.worker.postMessage({
      batchId,
      items: batch.map((item) => item.data),
    });
  }
}

// Memory-efficient data streaming
class StreamingWorker {
  constructor(workerPath) {
    this.worker = new Worker(workerPath);
    this.chunkSize = 1024 * 1024; // 1MB chunks
  }

  async processLargeData(data) {
    const chunks = this.createChunks(data);
    const results = [];

    for (let i = 0; i < chunks.length; i++) {
      const result = await this.processChunk(chunks[i], i, chunks.length);
      results.push(result);

      // Report progress
      this.onProgress?.({
        current: i + 1,
        total: chunks.length,
        percentage: ((i + 1) / chunks.length) * 100,
      });
    }

    return this.combineResults(results);
  }

  createChunks(data) {
    const chunks = [];
    const dataLength = data.length;

    for (let i = 0; i < dataLength; i += this.chunkSize) {
      chunks.push(data.slice(i, i + this.chunkSize));
    }

    return chunks;
  }

  processChunk(chunk, index, total) {
    return new Promise((resolve) => {
      this.worker.onmessage = (e) => resolve(e.data);

      this.worker.postMessage({
        chunk,
        chunkIndex: index,
        totalChunks: total,
      });
    });
  }

  combineResults(results) {
    // Combine based on data type
    if (results[0] instanceof Array) {
      return results.flat();
    } else if (typeof results[0] === 'string') {
      return results.join('');
    } else {
      return results;
    }
  }
}

Error Handling and Debugging

// Enhanced worker with error handling
class RobustWorker {
  constructor(workerPath, options = {}) {
    this.workerPath = workerPath;
    this.options = {
      maxRetries: 3,
      retryDelay: 1000,
      timeout: 30000,
      ...options,
    };

    this.initWorker();
  }

  initWorker() {
    this.worker = new Worker(this.workerPath);
    this.setupErrorHandling();
    this.setupHeartbeat();
  }

  setupErrorHandling() {
    this.worker.onerror = (error) => {
      console.error('Worker error:', error);
      this.handleError(error);
    };

    this.worker.onmessageerror = (error) => {
      console.error('Worker message error:', error);
      this.handleError(error);
    };
  }

  setupHeartbeat() {
    this.heartbeatInterval = setInterval(() => {
      this.sendHeartbeat();
    }, 5000);

    this.lastHeartbeat = Date.now();
  }

  sendHeartbeat() {
    const timeout = setTimeout(() => {
      console.error('Worker heartbeat timeout');
      this.restart();
    }, 3000);

    this.worker.postMessage({ type: 'heartbeat' });

    const messageHandler = (e) => {
      if (e.data.type === 'heartbeat-response') {
        clearTimeout(timeout);
        this.lastHeartbeat = Date.now();
        this.worker.removeEventListener('message', messageHandler);
      }
    };

    this.worker.addEventListener('message', messageHandler);
  }

  handleError(error) {
    this.errorCount = (this.errorCount || 0) + 1;

    if (this.errorCount <= this.options.maxRetries) {
      console.log(`Retrying worker (attempt ${this.errorCount})...`);
      setTimeout(() => this.restart(), this.options.retryDelay);
    } else {
      console.error('Worker failed after max retries');
      this.onFatalError?.(error);
    }
  }

  restart() {
    this.cleanup();
    this.errorCount = 0;
    this.initWorker();
    this.onRestart?.();
  }

  cleanup() {
    clearInterval(this.heartbeatInterval);
    this.worker.terminate();
  }

  postMessage(data) {
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error('Worker timeout'));
      }, this.options.timeout);

      const messageHandler = (e) => {
        if (e.data.id === data.id) {
          clearTimeout(timeout);
          this.worker.removeEventListener('message', messageHandler);

          if (e.data.error) {
            reject(new Error(e.data.error));
          } else {
            resolve(e.data.result);
          }
        }
      };

      this.worker.addEventListener('message', messageHandler);
      this.worker.postMessage(data);
    });
  }
}

// Worker-side error handling
self.addEventListener('message', async function (e) {
  const { id, type, data } = e.data;

  try {
    if (type === 'heartbeat') {
      self.postMessage({ type: 'heartbeat-response' });
      return;
    }

    const result = await processData(data);

    self.postMessage({
      id,
      result,
    });
  } catch (error) {
    self.postMessage({
      id,
      error: error.message,
      stack: error.stack,
    });
  }
});

// Debug utilities
class WorkerDebugger {
  static enableLogging(worker) {
    const originalPostMessage = worker.postMessage.bind(worker);

    worker.postMessage = function (data) {
      console.log('[Worker Send]', data);
      originalPostMessage(data);
    };

    const originalOnMessage = worker.onmessage;
    worker.onmessage = function (e) {
      console.log('[Worker Receive]', e.data);
      if (originalOnMessage) {
        originalOnMessage.call(this, e);
      }
    };
  }

  static profileWorker(worker, operation) {
    const startTime = performance.now();
    const startMemory = performance.memory?.usedJSHeapSize;

    return new Promise((resolve) => {
      worker.onmessage = (e) => {
        const endTime = performance.now();
        const endMemory = performance.memory?.usedJSHeapSize;

        resolve({
          result: e.data,
          duration: endTime - startTime,
          memoryUsed: endMemory - startMemory,
        });
      };

      worker.postMessage(operation);
    });
  }
}

Best Practices

  1. Choose the right worker type

    // Dedicated worker for isolated tasks
    const dedicated = new Worker('dedicated-task.js');
    
    // Shared worker for cross-tab communication
    const shared = new SharedWorker('shared-state.js');
    
  2. Optimize data transfer

    // Use transferable objects for large data
    const buffer = new ArrayBuffer(1024 * 1024);
    worker.postMessage({ buffer }, [buffer]);
    
  3. Implement proper error handling

    worker.onerror = (error) => {
      console.error('Worker error:', error);
      // Implement recovery strategy
    };
    
  4. Clean up workers

    // Terminate when done
    worker.terminate();
    
    // Or from inside worker
    self.close();
    

Conclusion

Web Workers are powerful for:

  • CPU-intensive tasks without blocking the UI
  • Parallel processing for better performance
  • Background operations like data processing
  • Real-time calculations and updates
  • Improved user experience with responsive interfaces

Key takeaways:

  • Use workers for heavy computations
  • Optimize data transfer with transferable objects
  • Implement worker pools for concurrent tasks
  • Handle errors and implement recovery strategies
  • Profile and monitor worker performance
  • Clean up resources properly

Master Web Workers to build high-performance JavaScript applications!