Skip to main content
Worker API 仍处于实验阶段(尤其是终止 workers 功能)。我们正在积极改进这一点。
Worker 允许你启动并与一个运行在独立线程上的新的 JavaScript 实例通信,同时与主线程共享 I/O 资源。 Bun 实现了 Web Workers API 的一个简化版本,同时添加了扩展,使其更适合服务器端的使用场景。与 Bun 的其他部分一样,Bun 中的 Worker 原生支持 CommonJS、ES 模块、TypeScript、JSX、TSX 等,无需额外构建步骤。

创建一个 Worker

与浏览器中一样,Worker 是全局对象。使用它可以创建一个新的工作线程。

在主线程中

https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const worker = new Worker("./worker.ts");

worker.postMessage("hello");
worker.onmessage = event => {
  console.log(event.data);
};

工作线程

worker.ts
// 防止 TypeScript 报错
declare var self: Worker;

self.onmessage = (event: MessageEvent) => {
  console.log(event.data);
  postMessage("world");
};
为防止在使用 self 时出现 TypeScript 报错,请在工作线程文件顶部添加以下声明:
declare var self: Worker;
你可以在工作线程代码中使用 importexport 语法。与浏览器不同的是,不需要指定 {type: "module"} 来使用 ES 模块。 为了简化错误处理,初始脚本会在调用 new Worker(url) 时立即解析。
const worker = new Worker("/not-found.js");
// 会立即抛出错误
传递给 Worker 的路径会相对于项目根目录解析(类似于在命令行运行 bun ./path/to/file.js)。

preload - 在 worker 启动前加载模块

你可以通过 preload 选项传递一个模块路径数组,以便在 worker 启动前加载这些模块。这在你想确保某些代码(如加载 OpenTelemetry、Sentry、DataDog 等)总是在应用启动前加载时非常有用。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const worker = new Worker("./worker.ts", {
  preload: ["./load-sentry.js"],
});
--preload CLI 参数一样,preload 选项会在 worker 启动前处理。 你也可以向 preload 选项传入单个字符串:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const worker = new Worker("./worker.ts", {
  preload: "./load-sentry.js",
});

blob: URL

你也可以向 Worker 传入 blob: URL。这对于从字符串或其他来源创建 worker 很有用。
const blob = new Blob([`self.onmessage = (event: MessageEvent) => postMessage(event.data)`], {
  type: "application/typescript",
});
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
和 Bun 的其他部分一样,从 blob: URL 创建的 workers 也默认支持 TypeScript、JSX 等文件类型。你可以通过设置 type 或在 File 构造函数里传入文件名来告诉它使用 TypeScript。
const file = new File([`self.onmessage = (event: MessageEvent) => postMessage(event.data)`], "worker.ts");
const url = URL.createObjectURL(file);
const worker = new Worker(url);

"open" 事件

当 worker 创建完成并准备好接收消息时,会发出 "open" 事件。你可以使用该事件向 worker 发送初始消息。(此事件在浏览器中不存在)
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const worker = new Worker(new URL("worker.ts", import.meta.url).href);

worker.addEventListener("open", () => {
  console.log("worker 已准备好");
});
消息会自动排队等待发送,直到 worker 准备就绪,因此你无需等待 "open" 事件即可发送消息。

使用 postMessage 发送消息

要发送消息,使用 worker.postMessageself.postMessage。这利用了 HTML 结构化克隆算法

性能优化

Bun 为 postMessage 包含了优化的快速路径,以显著提升常用数据类型的性能: 字符串快速路径 - 当只发送纯字符串时,Bun 直接绕过结构化克隆算法,显著减少序列化开销。 简单对象快速路径 - 对于仅包含原始值(字符串、数字、布尔值、null 和 undefined)的普通对象,Bun 使用优化的序列化路径,直接存储属性,无需完整的结构化克隆。 简单对象快速路径适用条件:
  • 是普通对象且没有修改原型链
  • 只包含可枚举、可配置的数据属性
  • 没有索引属性或 getter/setter 方法
  • 所有属性值都是原始类型或字符串
有了这些快速路径,Bun 的 postMessage 性能可提升 2 到 241 倍,消息长度不再显著影响性能。 Bun(含快速路径):
postMessage({ prop: 11 字符串, ...9 个其他属性 }) - 648 纳秒
postMessage({ prop: 14 KB 字符串, ...9 个其他属性 }) - 719 纳秒
postMessage({ prop: 3 MB 字符串, ...9 个其他属性 }) - 1.26 微秒
Node.js v24.6.0(对比):
postMessage({ prop: 11 字符串, ...9 个其他属性 }) - 1.19 微秒
postMessage({ prop: 14 KB 字符串, ...9 个其他属性 }) - 2.69 微秒
postMessage({ prop: 3 MB 字符串, ...9 个其他属性 }) - 304 微秒
// 字符串快速路径 - 优化处理
postMessage("Hello, worker!");

// 简单对象快速路径 - 优化处理
postMessage({
  message: "Hello",
  count: 42,
  enabled: true,
  data: null,
});

// 复杂对象仍然可用,但会使用标准结构化克隆
postMessage({
  nested: { deep: { object: true } },
  date: new Date(),
  buffer: new ArrayBuffer(8),
});
// 在工作线程中,`postMessage` 自动“路由”至父线程。
postMessage({ hello: "world" });

// 在主线程中
worker.postMessage({ hello: "world" });
接收消息可使用 worker 或主线程的 message 事件处理器
// 工作线程:
self.addEventListener("message", event => {
  console.log(event.data);
});
// 或者使用简写:
// self.onmessage = fn

// 如果在主线程:
worker.addEventListener("message", event => {
  console.log(event.data);
});
// 或者使用简写:
// worker.onmessage = fn

终止工作线程

Worker 实例的事件循环没有待处理任务时,它会自动终止。在全局范围或任何 MessagePort 上附加 "message" 监听器会保持事件循环活动。要强制终止 Worker,调用 worker.terminate()
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const worker = new Worker(new URL("worker.ts", import.meta.url).href);

// ...一段时间后
worker.terminate();
这会让 worker 尽快退出。

process.exit()

工作线程可以调用 process.exit() 自行终止,但这不会终止主进程。与 Node.js 相同,process.on('beforeExit', callback)process.on('exit', callback) 会在工作线程中触发(不会在主线程触发),退出代码会通过 "close" 事件传递。

"close" 事件

当 worker 被终止时,会发出 "close" 事件。worker 实际终止可能需要时间,因此当 worker 被标记为已终止时会触发该事件。CloseEvent 包含传递给 process.exit() 的退出代码,如果因其他原因关闭则为 0。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const worker = new Worker(new URL("worker.ts", import.meta.url).href);

worker.addEventListener("close", event => {
  console.log("worker 正在关闭");
});
此事件在浏览器中不存在。

生命周期管理

默认情况下,活跃的 Worker 会保持主进程运行,因此异步任务如 setTimeout 和 Promise 会让进程持续运行。附加 message 监听器也会保持 worker 活跃。

worker.unref()

调用 worker.unref() 可使 worker 不再阻止进程退出。这会将 worker 的生命周期与主进程解耦,相当于 Node.js 的 worker_threads 行为。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();
注意:worker.unref() 在浏览器中不可用。

worker.ref()

调用 worker.ref() 可以保持进程运行直到 Worker 终止。带引用的 worker 是默认行为,仍需要事件循环中有活动(如 "message" 监听器)来保持运行。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();
// 以后...
worker.ref();
你也可以通过向 Worker 传入 options 对象来设置:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const worker = new Worker(new URL("worker.ts", import.meta.url).href, {
  ref: false,
});
注意:worker.ref() 在浏览器中不可用。

使用 smol 模式降低内存使用

JavaScript 实例可能消耗大量内存。Bun 的 Worker 支持 smol 模式,可通过牺牲性能来降低内存占用。要启用 smol 模式,向 Worker 构造函数的 options 对象传入 smol: true
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const worker = new Worker("./i-am-smol.ts", {
  smol: true,
});
设置 smol: true 会将 JSC::HeapSize 设置为 Small,而非默认的 Large

环境数据共享

使用 setEnvironmentData()getEnvironmentData() 在主线程和工作线程间共享数据。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
import { setEnvironmentData, getEnvironmentData } from "worker_threads";

// 在主线程
setEnvironmentData("config", { apiUrl: "https://api.example.com" });

// 在工作线程
const config = getEnvironmentData("config");
console.log(config); // => { apiUrl: "https://api.example.com" }

Worker 事件

使用 process.emit() 监听 worker 创建事件:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
process.on("worker", worker => {
  console.log("新 worker 已创建:", worker.threadId);
});

Bun.isMainThread

你可以通过检测 Bun.isMainThread 来判断代码是否运行在主线程。
if (Bun.isMainThread) {
  console.log("我是主线程");
} else {
  console.log("我在 worker 中");
}
这对于基于当前线程类型有条件地运行代码非常有用。