Skillversitas

Web Workers

Web Workers allow running JavaScript on a separate background thread, separate from the main thread. This prevents heavy computations from blocking the UI. The main thread and the worker communicate through postMessage and onmessage.

Code Example

Basic Worker

// === worker.js (separate file) ===
self.onmessage = function (event) {
  const data = event.data;
  console.log('Worker received:', data);

  // Process the data
  const result = data.numbers.map(n => n * n);

  // Send the result back
  self.postMessage(result);
};

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

// Send data to the worker
worker.postMessage({ numbers: [1, 2, 3, 4, 5] });

// Receive the result from the worker
worker.onmessage = function (event) {
  console.log('Result from worker:', event.data);
  // [1, 4, 9, 16, 25]
};

// Handle errors in the worker
worker.onerror = function (error) {
  console.error('Worker error:', error.message, error.filename, error.lineno);
};

Worker with Functions

// === calculate-statistics.js ===
self.onmessage = function (event) {
  const { action, data } = event.data;

  switch (action) {
    case 'average':
      const avg = data.reduce((a, b) => a + b, 0) / data.length;
      self.postMessage({ action, result: avg });
      break;

    case 'median':
      const sorted = [...data].sort((a, b) => a - b);
      const mid = Math.floor(sorted.length / 2);
      const median = sorted.length % 2 !== 0
        ? sorted[mid]
        : (sorted[mid - 1] + sorted[mid]) / 2;
      self.postMessage({ action, result: median });
      break;

    case 'standardDeviation':
      const mean = data.reduce((a, b) => a + b, 0) / data.length;
      const squaredDiffs = data.map(n => Math.pow(n - mean, 2));
      const avgSquaredDiff = squaredDiffs.reduce((a, b) => a + b, 0) / data.length;
      self.postMessage({ action, result: Math.sqrt(avgSquaredDiff) });
      break;
  }
};

// === main.js ===
const worker = new Worker('calculate-statistics.js');

// Calculate the average
worker.postMessage({ action: 'average', data: [85, 90, 78, 92, 88] });

// Calculate the median
worker.postMessage({ action: 'median', data: [85, 90, 78, 92, 88] });

worker.onmessage = function (event) {
  const { action, result } = event.data;
  console.log(`${action}: ${result}`);
};

Code Explanation

postMessage() and onmessage()

// postMessage: send data to the worker
worker.postMessage(data);              // send simple data
worker.postMessage({ key: 'value' }); // send an object
worker.postMessage([1, 2, 3]);        // send an array

// onmessage: receive data from the worker
worker.onmessage = function (event) {
  const data = event.data; // the data sent by the worker
};

// or use addEventListener
worker.addEventListener('message', function (event) {
  console.log(event.data);
});

terminate()

// Force-stop the worker
// Useful if the process takes too long or the user cancels
const worker = new Worker('process.js');

// Set a timeout — if the worker does not finish within 10 seconds
const timeout = setTimeout(() => {
  worker.terminate();
  console.error('Worker timeout — dipaksa berhenti');
}, 10000);

worker.onmessage = function (event) {
  clearTimeout(timeout);
  console.log('Result:', event.data);
};

importScripts()

// === worker.js ===
// Import other scripts into the worker
importScripts('util.js', 'kalkulasi.js');

// Now you can use functions from util.js and kalkulasi.js
self.onmessage = function (event) {
  const result = calculateStatistics(event.data); // from kalkulasi.js
  self.postMessage(result);
};

Transferable Objects

// Send large data without copying — using a transfer
const largeArray = new Float64Array(1000000);
// fill in data...

// Transfer the ArrayBuffer to the worker (data is in the worker, gone in main)
worker.postMessage(largeArray.buffer, [largeArray.buffer]);

// In the worker:
self.onmessage = function (event) {
  const buffer = event.data;
  const array = new Float64Array(buffer);
  // process the array...
};

SharedWorker (Basics)

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

self.onconnect = function (event) {
  const port = event.ports[0];
  connections.push(port);

  port.onmessage = function (e) {
    // Broadcast to all connections
    connections.forEach(conn => {
      conn.postMessage(e.data);
    });
  };

  port.start();
};

// === main.js ===
const sharedWorker = new SharedWorker('shared-worker.js');
sharedWorker.port.start();

// Send a message
sharedWorker.port.postMessage('Hello from this tab');

// Receive a message
sharedWorker.port.onmessage = function (event) {
  console.log('Dari SharedWorker:', event.data);
};

Real-World Examples

Calculating Grade Statistics Without Blocking the UI

// === calculate-grade-worker.js ===
self.onmessage = function (event) {
  const { action, data } = event.data;

  let result;

  switch (action) {
    case 'gradeSummary': {
      // Simulate a heavy computation
      const { gradeArray } = data;

      const count = gradeArray.length;
      const total = gradeArray.reduce((a, b) => a + b, 0);
      const average = total / count;

      // Standard deviation
      const squaredDiffs = gradeArray.map(n => Math.pow(n - average, 2));
      const variance = squaredDiffs.reduce((a, b) => a + b, 0) / count;
      const stdDev = Math.sqrt(variance);

      // Median
      const sorted = [...gradeArray].sort((a, b) => a - b);
      const mid = Math.floor(sorted.length / 2);
      const median = sorted.length % 2 !== 0
        ? sorted[mid]
        : (sorted[mid - 1] + sorted[mid]) / 2;

      // Grade distribution
      const distribution = {
        A: gradeArray.filter(n => n >= 85).length,
        B: gradeArray.filter(n => n >= 70 && n < 85).length,
        C: gradeArray.filter(n => n >= 55 && n < 70).length,
        D: gradeArray.filter(n => n >= 40 && n < 55).length,
        E: gradeArray.filter(n => n < 40).length
      };

      // Simulate heavy processing (a large loop)
      let dummy = 0;
      for (let i = 0; i < 10000000; i++) {
        dummy += Math.sqrt(i);
      }

      result = {
        count,
        average: average.toFixed(2),
        median,
        standardDeviation: stdDev.toFixed(2),
        distribution
      };
      break;
    }

    default:
      result = { error: 'Unknown action' };
  }

  self.postMessage({ action, result });
};

// === main.js ===
const worker = new Worker('calculate-grade-worker.js');

// UI — the process button
document.querySelector('#btn-process').addEventListener('click', function () {
  const grade = generateRandomGrade(500); // an array of 500 grades

  this.disabled = true;
  this.textContent = 'Calculating...';

  // Send to the worker
  worker.postMessage({
    action: 'gradeSummary',
    data: { gradeArray: grade }
  });
});

// Receive the result
worker.onmessage = function (event) {
  const { action, result } = event.data;

  if (action === 'gradeSummary') {
    renderStatistics(result);

    document.querySelector('#btn-process').disabled = false;
    document.querySelector('#btn-process').textContent = 'Process Grade';
  }
};

// The UI stays responsive while the heavy computation runs
function generateRandomGrade(count) {
  return Array.from({ length: count }, () =>
    Math.floor(Math.random() * 100)
  );
}

function renderStatistics(data) {
  document.querySelector('#average').textContent = data.average;
  document.querySelector('#median').textContent = data.median;
  document.querySelector('#std-dev').textContent = data.standardDeviation;
  // render the distribution...
}

Multiple Workers for Parallel Processing

// Split the data into several chunks and process them in parallel
async function processGradeParalel(allGrades) {
  const CHUNK_SIZE = 100;
  const chunks = [];

  for (let i = 0; i < allGrades.length; i += CHUNK_SIZE) {
    chunks.push(allGrades.slice(i, i + CHUNK_SIZE));
  }

  // Create a worker for each chunk
  const workers = chunks.map((chunk, index) => {
    return new Promise((resolve) => {
      const worker = new Worker('calculate-grade-worker.js');

      worker.onmessage = function (event) {
        resolve(event.data.result);
        worker.terminate();
      };

      worker.postMessage({
        action: 'gradeSummary',
        data: { gradeArray: chunk }
      });
    });
  });

  // Run all workers in parallel
  const results = await Promise.all(workers);

  // Merge the results
  return results;
}

Worker with Error Handling

class SafeWorker {
  constructor(scriptUrl) {
    this.scriptUrl = scriptUrl;
    this.worker = null;
  }

  run(data, timeout = 10000) {
    return new Promise((resolve, reject) => {
      this.worker = new Worker(this.scriptUrl);

      const timer = setTimeout(() => {
        this.worker.terminate();
        reject(new Error('Worker timeout'));
      }, timeout);

      this.worker.onmessage = function (event) {
        clearTimeout(timer);
        resolve(event.data);
      };

      this.worker.onerror = function (error) {
        clearTimeout(timer);
        reject(new Error(`Worker error: ${error.message}`));
      };

      this.worker.postMessage(data);
    });
  }

  terminate() {
    if (this.worker) {
      this.worker.terminate();
      this.worker = null;
    }
  }
}

// Usage
const safeWorker = new SafeWorker('calculate-grade-worker.js');

async function process() {
  try {
    const result = await safeWorker.run({
      action: 'gradeSummary',
      data: { gradeArray: [85, 90, 78] }
    });
    console.log('Result:', result);
  } catch (error) {
    console.error('Failed:', error.message);
  }
}

When to Use Web Workers

// USE workers for:
// - Statistical calculations on large datasets
// - Sorting/filtering large arrays
// - Encryption/decryption
// - Image processing
// - Complex algorithms

// NO worker NEEDED for:
// - Fetch API (already asynchronous)
// - Simple DOM manipulation
// - Small computations
// - Event handlers

Conclusion

Web Workers run JavaScript code on a background thread so the UI stays responsive. Communication happens through postMessage and onmessage. Use terminate() to stop a worker, importScripts() to import other scripts, and transferable objects for large data. For a campus-heavy UI with lots of student data and statistics, workers keep performance smooth.