Worker Patterns
The request/response pattern
Section titled “The request/response pattern”Workers are asynchronous and may handle many tasks. To match a response to the right request, attach a unique id to each message:
let nextId = 0;const pending = {};worker.onmessage = (e) => { const { id, result } = e.data; if (pending[id]) { pending[id](result); delete pending[id]; }};function ask(payload) { return new Promise((resolve) => { const id = ++nextId; pending[id] = resolve; worker.postMessage({ id, payload }); });}// worker.jsself.onmessage = (e) => { const { id, payload } = e.data; self.postMessage({ id, result: process(payload) });};A tiny RPC wrapper
Section titled “A tiny RPC wrapper”Wrapping the pattern above into a small helper makes calling the worker feel like a regular async function call.
function createRpc(worker) { let seq = 0; const map = {}; worker.onmessage = (e) => { const cb = map[e.data.id]; if (cb) { cb(e.data.result); delete map[e.data.id]; } }; return { call(method, args) { return new Promise((res) => { const id = ++seq; map[id] = res; worker.postMessage({ id, method, args }); }); } };}Error handling with onerror
Section titled “Error handling with onerror”Uncaught errors inside a worker fire worker.onerror in the main thread — not window.onerror. Always attach a handler:
worker.onerror = (event) => { console.error('Worker error:', event.message, 'at', event.filename, ':', event.lineno); event.preventDefault(); // suppress browser console error};You can also attach self.onerror inside the worker itself to catch async errors before they bubble up to the main thread.
When to use a worker
Section titled “When to use a worker”| Use a worker | Do NOT use a worker |
|---|---|
| CPU-intensive loops (crypto, image processing, compression) | Simple DOM reads/writes |
| Parsing large JSON or binary data | Short tasks < 5 ms |
| Real-time data processing (audio, video) | Tasks needing direct DOM access |
| Keeping the UI at 60 fps during heavy work | One-off simple async fetches (use fetch directly) |