Web APIs Cheatsheet

Web Workers

Use this Web APIs reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Dedicated Workers

// main.js — spawn and communicate
const worker = new Worker('./worker.js', { type: 'module' }); // 'classic' default

worker.postMessage({ action: 'process', data: [1, 2, 3] });

worker.addEventListener('message', e => {
  console.log('Result:', e.data);
});

worker.addEventListener('error', e => {
  console.error(e.message, e.filename, e.lineno);
  e.preventDefault(); // suppress console error
});

worker.addEventListener('messageerror', e => {
  // Fired when message cannot be deserialized
  console.error(e);
});

worker.terminate(); // immediately stop worker
// worker.js — inside the worker
self.addEventListener('message', e => {
  const { action, data } = e.data;
  if (action === 'process') {
    const result = data.map(n => n * 2);
    self.postMessage(result);
  }
});

// Alternatively
onmessage = e => {
  postMessage(e.data * 2);
};

// Error inside worker propagates to main thread 'error' event

Worker Constructor Options

OptionValuesDescription
type'classic' (default) 'module''module' enables ES imports inside worker
namestringDebug name, visible in DevTools
credentials'omit' 'same-origin' 'include'For module workers

Transferable Objects (zero-copy)

Transferring ownership moves the buffer — the original becomes unusable. Much faster than copying large data.

const buffer = new ArrayBuffer(1024 * 1024); // 1 MB

// Transfer to worker (buffer is now detached in main thread)
worker.postMessage({ buffer }, [buffer]);

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

// Transferable types:
// ArrayBuffer, MessagePort, ReadableStream, WritableStream,
// TransformStream, ImageBitmap, OffscreenCanvas, VideoFrame

Shared Memory (SharedArrayBuffer + Atomics)

Requires COOP/COEP headers: Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp.

// main.js
const sab = new SharedArrayBuffer(4); // 4 bytes
const arr = new Int32Array(sab);
worker.postMessage(sab); // share (not transfer)

// worker.js
onmessage = e => {
  const arr = new Int32Array(e.data);
  Atomics.store(arr, 0, 42);
  Atomics.notify(arr, 0, 1); // wake waiting thread
};

// main.js — wait for value change
Atomics.wait(arr, 0, 0);         // blocks — do NOT use on main thread!
// On main thread use Atomics.waitAsync instead:
const { async, value } = Atomics.waitAsync(arr, 0, 0);
if (async) value.then(v => console.log('Woken:', v));

// Atomic operations
Atomics.add(arr, 0, 1);     // arr[0] += 1
Atomics.sub(arr, 0, 1);
Atomics.and(arr, 0, 0xFF);
Atomics.or(arr, 0, 0x01);
Atomics.xor(arr, 0, 0xFF);
Atomics.exchange(arr, 0, 99);           // set and return old
Atomics.compareExchange(arr, 0, 99, 0); // CAS: if arr[0]===99, set to 0
Atomics.load(arr, 0);

Shared Workers

Shared across multiple tabs/windows on the same origin. Communicate through MessagePort.

// main.js
const sw = new SharedWorker('./shared-worker.js', { name: 'shared' });
sw.port.start();
sw.port.postMessage('hello');
sw.port.addEventListener('message', e => console.log(e.data));
sw.onerror = e => console.error(e);
// shared-worker.js
const ports = new Set();

self.addEventListener('connect', e => {
  const port = e.ports[0];
  ports.add(port);
  port.start();
  port.addEventListener('message', e => {
    // broadcast to all connected ports
    for (const p of ports) p.postMessage(`Echo: ${e.data}`);
  });
  port.addEventListener('close', () => ports.delete(port));
});

Service Workers

Act as a network proxy. Lifecycle: install → activate → fetch. Scope is based on script location.

// Register
if ('serviceWorker' in navigator) {
  const reg = await navigator.serviceWorker.register('/sw.js', { scope: '/' });
  reg.update();          // force update check
  reg.unregister();      // unregister
  reg.installing;        // installing SW
  reg.waiting;           // waiting SW (update ready)
  reg.active;            // active SW
}

// Communicate with active SW
navigator.serviceWorker.controller?.postMessage('ping');
navigator.serviceWorker.addEventListener('message', e => console.log(e.data));
// sw.js — Service Worker
self.addEventListener('install', e => {
  e.waitUntil(
    caches.open('v1').then(c => c.addAll(['/index.html', '/app.js']))
  );
  self.skipWaiting(); // activate immediately, bypass waiting
});

self.addEventListener('activate', e => {
  e.waitUntil(
    caches.keys().then(keys =>
      Promise.all(keys.filter(k => k !== 'v1').map(k => caches.delete(k)))
    )
  );
  self.clients.claim(); // take control of open pages immediately
});

self.addEventListener('fetch', e => {
  e.respondWith(
    caches.match(e.request).then(cached => cached ?? fetch(e.request))
  );
});

// Background Sync
self.addEventListener('sync', e => {
  if (e.tag === 'sync-messages') e.waitUntil(flushQueue());
});

// Push notifications
self.addEventListener('push', e => {
  const data = e.data?.json();
  e.waitUntil(self.registration.showNotification(data.title, data));
});

// Notify clients
self.clients.matchAll().then(clients =>
  clients.forEach(c => c.postMessage({ type: 'UPDATE' }))
);

Worker Scope — Available APIs

APIDedicatedSharedService
fetchYesYesYes
XMLHttpRequestYesYesNo
IndexedDBYesYesYes
Cache APIYesYesYes
WebSocketsYesYesYes
setTimeout/setIntervalYesYesNo
importScripts()Yes (classic)Yes (classic)Yes (classic)
ES importYes (module)Yes (module)No
DOMNoNoNo
windowNoNoNo
navigatorPartialPartialPartial
locationYes (WorkerLocation)YesYes
selfYes (WorkerGlobalScope)YesYes (ServiceWorkerGlobalScope)

Module Workers and Imports

// type: 'module' — can use static and dynamic import
const worker = new Worker('./worker.js', { type: 'module' });

// worker.js (module)
import { heavyTask } from './utils.js';
onmessage = e => postMessage(heavyTask(e.data));
// Classic worker — use importScripts
importScripts('https://cdn.example.com/lib.js', './utils.js');

Worklet (audio, paint, layout)

// CSS Paint Worklet (Houdini)
await CSS.paintWorklet.addModule('paint-worklet.js');

// Audio Worklet
await audioCtx.audioWorklet.addModule('processor.js');
const node = new AudioWorkletNode(audioCtx, 'my-processor');

// paint-worklet.js
registerPaint('my-painter', class {
  paint(ctx, geom, props) { /* Canvas 2D API subset */ }
});

Common Patterns

// Promisified worker request/response
function callWorker(worker, message) {
  return new Promise((resolve, reject) => {
    const id = crypto.randomUUID();
    const handler = e => {
      if (e.data.id !== id) return;
      worker.removeEventListener('message', handler);
      e.data.error ? reject(new Error(e.data.error)) : resolve(e.data.result);
    };
    worker.addEventListener('message', handler);
    worker.postMessage({ ...message, id });
  });
}

// worker.js
onmessage = async e => {
  try {
    const result = await doWork(e.data);
    postMessage({ id: e.data.id, result });
  } catch (err) {
    postMessage({ id: e.data.id, error: err.message });
  }
};
// Inline worker from blob URL
const code = `onmessage = e => postMessage(e.data * 2);`;
const blob = new Blob([code], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));