Documentation Index
Fetch the complete documentation index at: https://mintlify.com/zhcndoc/bun/llms.txt
Use this file to discover all available pages before exploring further.
Bun implements the Web Workers API for running JavaScript in parallel threads. Workers are useful for CPU-intensive tasks, parallel processing, and isolating code execution.
Creating a Worker
const worker = new Worker("worker.ts");
worker.postMessage({ task: "compute", data: [1, 2, 3] });
worker.onmessage = (event) => {
console.log("Result:", event.data);
};
In worker.ts:
self.onmessage = (event) => {
const { task, data } = event.data;
if (task === "compute") {
const result = data.reduce((a, b) => a + b, 0);
self.postMessage(result);
}
};
Worker Options
Module Type
Workers can be ES modules or classic scripts:
// ES Module (default in Bun)
const worker = new Worker("worker.ts", { type: "module" });
// Classic script
const worker = new Worker("worker.js", { type: "classic" });
Worker Name
Name workers for debugging:
const worker = new Worker("worker.ts", { name: "DataProcessor" });
console.log(worker.name); // "DataProcessor"
Credentials
Control credential passing for module imports:
const worker = new Worker("worker.ts", {
credentials: "same-origin", // or "include", "omit"
});
Communication
Posting Messages
Send data between main thread and worker:
// Main thread
worker.postMessage({ type: "start", config: { threads: 4 } });
// Worker
self.onmessage = (event) => {
const { type, config } = event.data;
// Process message
};
Message Event
worker.addEventListener("message", (event) => {
console.log("Received:", event.data);
});
// Or using property
worker.onmessage = (event) => {
console.log("Received:", event.data);
};
Error Handling
worker.addEventListener("error", (event) => {
console.error("Worker error:", event.message);
console.error("File:", event.filename);
console.error("Line:", event.lineno);
});
worker.onerror = (event) => {
console.error("Error in worker:", event.message);
};
Unhandled Rejections
worker.addEventListener("messageerror", (event) => {
console.error("Message deserialization failed");
});
Transferring Data
Structured Clone
By default, data is cloned:
const data = { numbers: [1, 2, 3], text: "hello" };
worker.postMessage(data);
// `data` is cloned and can still be used
Supported types:
- Primitives (string, number, boolean)
- Objects and Arrays
Date, RegExp, Map, Set
ArrayBuffer, TypedArrays
Blob, File, ImageData
Transfer Objects
For better performance, transfer ownership:
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
// Transfer ownership to worker
worker.postMessage({ buffer }, [buffer]);
// buffer is now detached and unusable in main thread
console.log(buffer.byteLength); // 0
Transferable types:
ArrayBuffer
MessagePort
ReadableStream
WritableStream
TransformStream
Shared Memory
Use SharedArrayBuffer for shared memory:
// Main thread
const shared = new SharedArrayBuffer(1024);
const view = new Int32Array(shared);
worker.postMessage({ shared });
// Both threads can access the same memory
view[0] = 42;
// Worker thread
self.onmessage = ({ data }) => {
const view = new Int32Array(data.shared);
console.log(view[0]); // 42
view[0] = 100;
};
Worker Lifecycle
Terminating Workers
// Graceful shutdown
worker.postMessage({ type: "shutdown" });
// Force terminate
worker.terminate();
Inside the worker:
self.onmessage = (event) => {
if (event.data.type === "shutdown") {
// Cleanup
self.close(); // Terminate from inside
}
};
Reference Counting
Control whether worker keeps process alive:
// Worker keeps process alive (default)
worker.ref();
// Worker doesn't keep process alive
worker.unref();
Worker Scope
Global Objects
Inside a worker:
console.log(self); // WorkerGlobalScope
console.log(self.name); // Worker name if provided
// No window or document
console.log(typeof window); // undefined
console.log(typeof document); // undefined
Available APIs
Workers have access to:
console
setTimeout, setInterval, setImmediate
fetch, WebSocket
crypto, TextEncoder, TextDecoder
URL, URLSearchParams
Blob, File, FormData
ReadableStream, WritableStream
- Import maps and ES modules
Not Available
- DOM APIs (document, window)
- Most Bun-specific APIs (Bun.serve, etc.)
- Process-level APIs (process.exit)
Import Maps in Workers
Workers support import maps:
// Main thread
const worker = new Worker("worker.ts", {
type: "module",
});
// worker.ts can use imports
import { helper } from "./utils.ts";
import lodash from "lodash";
TypeScript
Bun automatically transpiles TypeScript in workers:
// worker.ts
interface Task {
id: number;
payload: unknown;
}
self.onmessage = (event: MessageEvent<Task>) => {
const { id, payload } = event.data;
// Type-safe code
};
Nested Workers
Workers can create other workers:
// worker.ts
const subWorker = new Worker("sub-worker.ts");
subWorker.postMessage({ task: "process" });
subWorker.onmessage = (event) => {
// Forward result to main thread
self.postMessage(event.data);
};
Common Patterns
Worker Pool
class WorkerPool {
workers: Worker[] = [];
queue: Array<{ data: any; resolve: Function }> = [];
constructor(size: number, script: string) {
for (let i = 0; i < size; i++) {
const worker = new Worker(script);
worker.onmessage = (event) => {
const next = this.queue.shift();
if (next) {
worker.postMessage(next.data);
}
};
this.workers.push(worker);
}
}
async run(data: any): Promise<any> {
return new Promise((resolve) => {
const worker = this.workers.find(w => /* is idle */);
if (worker) {
worker.postMessage(data);
} else {
this.queue.push({ data, resolve });
}
});
}
terminate() {
this.workers.forEach(w => w.terminate());
}
}
const pool = new WorkerPool(4, "worker.ts");
for (let i = 0; i < 100; i++) {
await pool.run({ task: i });
}
pool.terminate();
Request-Response Pattern
// Main thread
let messageId = 0;
const pending = new Map();
worker.onmessage = (event) => {
const { id, result, error } = event.data;
const { resolve, reject } = pending.get(id);
pending.delete(id);
if (error) reject(error);
else resolve(result);
};
function request(data: any): Promise<any> {
return new Promise((resolve, reject) => {
const id = messageId++;
pending.set(id, { resolve, reject });
worker.postMessage({ id, data });
});
}
const result = await request({ type: "compute", value: 42 });
// Worker
self.onmessage = async (event) => {
const { id, data } = event.data;
try {
const result = await processData(data);
self.postMessage({ id, result });
} catch (error) {
self.postMessage({ id, error: error.message });
}
};
Progress Updates
// Worker
self.onmessage = async (event) => {
const total = event.data.items.length;
for (let i = 0; i < total; i++) {
await processItem(event.data.items[i]);
// Send progress
self.postMessage({
type: "progress",
current: i + 1,
total,
});
}
self.postMessage({ type: "complete" });
};
// Main thread
worker.onmessage = (event) => {
if (event.data.type === "progress") {
const pct = (event.data.current / event.data.total) * 100;
console.log(`Progress: ${pct.toFixed(1)}%`);
} else if (event.data.type === "complete") {
console.log("Done!");
}
};
When to Use Workers
Good use cases:
- CPU-intensive computations
- Parsing large data files
- Image/video processing
- Cryptographic operations
- Parallel data processing
Not ideal for:
- I/O operations (use async instead)
- Simple computations (overhead not worth it)
- DOM manipulation (not available in workers)
Minimizing Communication
Message passing has overhead:
// Bad - many small messages
for (let i = 0; i < 1000; i++) {
worker.postMessage({ index: i });
}
// Good - batch messages
worker.postMessage({ items: Array.from({ length: 1000 }, (_, i) => i) });
Using Transfers
Transfer large buffers instead of cloning:
// Slow - clones 100MB
const data = new Uint8Array(100 * 1024 * 1024);
worker.postMessage({ data });
// Fast - transfers ownership
worker.postMessage({ data }, [data.buffer]);
Debugging Workers
// Named workers help identify threads
const worker = new Worker("worker.ts", { name: "ImageProcessor" });
// Use console.log in workers
self.onmessage = (event) => {
console.log("Worker received:", event.data);
};
// Catch errors
worker.onerror = (event) => {
console.error(`Error in ${event.filename}:${event.lineno}`);
console.error(event.message);
};
Compatibility
Bun’s Web Workers are compatible with:
- Browsers (Chrome, Firefox, Safari, Edge)
- Deno
- Cloudflare Workers (with some limitations)
Differences from Node.js
Bun implements the standard Web Workers API, not Node.js worker_threads:
// Bun (Web Workers)
const worker = new Worker("worker.ts");
// Node.js (worker_threads)
const { Worker } = require('worker_threads');
const worker = new Worker('./worker.js');
Migration tips:
- Use
postMessage instead of parentPort.postMessage
- Use
self.onmessage instead of parentPort.on('message')
- No
workerData - pass data via postMessage