构建文档参考指南博客
Bun

Workers

🚧Worker API 仍处于实验阶段(特别是终止工作线程的功能)。我们正在积极改进它。

Worker 允许您启动新的 JavaScript 实例并在独立线程上运行,同时与主线程共享 I/O 资源。

Bun 实现了一个 Web Workers API 的简化版本,并进行了一些扩展,使其更适合服务器端使用场景。与 Bun 的其他部分一样,Bun 中的 Worker 支持 CommonJS、ES Modules、TypeScript、JSX、TSX 等,无需额外的构建步骤。

创建 Worker

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

从主线程

主线程
const worker = new Worker("./worker.ts");

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

工作线程

worker.ts (工作线程)
// prevents TS errors
declare var self: Worker;

self.onmessage = (event: MessageEvent) => {
  console.log(event.data);
  postMessage("world");
};

为了在使用 self 时避免 TypeScript 错误,请在您的工作线程文件的顶部添加此行。

declare var self: Worker;

您可以在工作线程代码中使用 importexport 语法。与浏览器不同,无需指定 {type: "module"} 即可使用 ES Modules。

为了简化错误处理,初始脚本的加载会在调用 new Worker(url) 时解决。

const worker = new Worker("/not-found.js");
// throws an error immediately

传递给 Worker 的说明符是相对于项目根目录解析的(就像键入 bun ./path/to/file.js 一样)。

preload - 在工作线程启动前加载模块

您可以将模块说明符数组传递给 preload 选项,以便在工作线程启动前加载模块。当您希望确保某些代码在应用程序启动前始终加载时,这非常有用,例如加载 OpenTelemetry、Sentry、DataDog 等。

const worker = new Worker("./worker.ts", {
  preload: ["./load-sentry.js"],
});

--preload CLI 参数一样,preload 选项在工作线程启动前处理。

您也可以将单个字符串传递给 preload 选项。

const worker = new Worker("./worker.ts", {
  preload: "./load-sentry.js",
});

此功能在 Bun v1.1.35 中添加。

blob: URL

从 Bun v1.1.13 开始,您还可以将 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 创建的工作线程开箱即支持 TypeScript、JSX 和其他文件类型。您可以通过 type 或通过向 File 构造函数传递 filename 来与 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"

当工作线程创建并准备好接收消息时,会发出 "open" 事件。这可以用于在工作线程准备好后向其发送初始消息。(此事件在浏览器中不存在。)

const worker = new Worker(new URL("worker.ts", import.meta.url).href);

worker.addEventListener("open", () => {
  console.log("worker is ready");
});

消息会自动排队,直到工作线程准备就绪,因此无需等待 "open" 事件来发送消息。

使用 postMessage 进行通信

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

性能优化

Bun 为 postMessage 包含优化的快速路径,以极大地提高常见数据类型的性能。

字符串快速路径 - 当发送纯字符串值时,Bun 完全绕过了结构化克隆算法,实现了显著的性能提升,而无需任何序列化开销。

简单对象快速路径 - 对于仅包含原始值(字符串、数字、布尔值、null、undefined)的普通对象,Bun 使用优化的序列化路径,直接存储属性,而无需进行完整的结构化克隆。

当对象满足以下条件时,将激活简单对象快速路径:

  • 是一个没有原型链修改的普通对象
  • 仅包含可枚举、可配置的数据属性
  • 没有索引属性或 getter/setter 方法
  • 所有属性值都是原始值或字符串

通过这些快速路径,Bun 的 postMessage 性能提升了 **2-241 倍**,因为消息长度不再对性能产生有意义的影响。

Bun(带快速路径)

postMessage({ prop: 11 chars string, ...9 more props }) - 648ns
postMessage({ prop: 14 KB string, ...9 more props })    - 719ns
postMessage({ prop: 3 MB string, ...9 more props })     - 1.26µs

Node.js v24.6.0(供比较)

postMessage({ prop: 11 chars string, ...9 more props }) - 1.19µs
postMessage({ prop: 14 KB string, ...9 more props })    - 2.69µs
postMessage({ prop: 3 MB string, ...9 more props })     - 304µs
// String fast path - optimized
postMessage("Hello, worker!");

// Simple object fast path - optimized
postMessage({
  message: "Hello",
  count: 42,
  enabled: true,
  data: null,
});

// Complex objects still work but use standard structured clone
postMessage({
  nested: { deep: { object: true } },
  date: new Date(),
  buffer: new ArrayBuffer(8),
});
// On the worker thread, `postMessage` is automatically "routed" to the parent thread.
postMessage({ hello: "world" });

// On the main thread
worker.postMessage({ hello: "world" });

要接收消息,请使用工作线程和主线程上的 message 事件处理器

// Worker thread:
self.addEventListener("message", event => {
  console.log(event.data);
});
// or use the setter:
// self.onmessage = fn

// if on the main thread
worker.addEventListener("message", event => {
  console.log(event.data);
});
// or use the setter:
// worker.onmessage = fn

终止工作线程

一旦工作线程的事件循环没有更多待处理工作,Worker 实例将自动终止。在全局或任何 MessagePort 上附加 "message" 监听器将使事件循环保持活动状态。要强制终止 Worker,请调用 worker.terminate()

const worker = new Worker(new URL("worker.ts", import.meta.url).href);

// ...some time later
worker.terminate();

这将导致工作线程尽快退出。

process.exit()

工作线程可以使用 process.exit() 终止自身。这不会终止主进程。与 Node.js 一样,process.on('beforeExit', callback)process.on('exit', callback) 会在工作线程上触发(而不是在主线程上),并且退出代码将传递给 "close" 事件。

"close"

当工作线程被终止时,会发出 "close" 事件。工作线程实际终止可能需要一些时间,因此在工作线程被标记为已终止时会发出此事件。CloseEvent 将包含传递给 process.exit() 的退出代码,如果因其他原因关闭则为 0。

const worker = new Worker(new URL("worker.ts", import.meta.url).href);

worker.addEventListener("close", event => {
  console.log("worker is being closed");
});

此事件在浏览器中不存在。

管理生命周期

默认情况下,活动的 Worker 会使主(生成)进程保持运行状态,因此像 setTimeout 和 promises 这样的异步任务会使进程保持运行。附加 message 监听器也将使 Worker 保持运行。

worker.unref()

要阻止运行中的工作线程使进程保持运行,请调用 worker.unref()。这会将工作线程的生命周期与主进程的生命周期解耦,并且等同于 Node.js 的 worker_threads 所做的操作。

const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();

注意:worker.unref() 在浏览器中不可用。

worker.ref()

要保持进程运行直到 Worker 终止,请调用 worker.ref()。ref'd worker 是默认行为,并且仍然需要事件循环中有其他操作(例如 "message" 监听器)才能使工作线程继续运行。

const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();
// later...
worker.ref();

或者,您也可以将 options 对象传递给 Worker

const worker = new Worker(new URL("worker.ts", import.meta.url).href, {
  ref: false,
});

注意:worker.ref() 在浏览器中不可用。

使用 smol 时的内存使用情况

JavaScript 实例可能会占用大量内存。Bun 的 Worker 支持 smol 模式,该模式可以减少内存使用,但会牺牲性能。要启用 smol 模式,请在 Worker 构造函数中将 smol: true 传递给 options 对象。

const worker = new Worker("./i-am-smol.ts", {
  smol: true,
});

smol 模式实际上做什么?

环境数据

使用 setEnvironmentData()getEnvironmentData() 在主线程和工作线程之间共享数据。

import { setEnvironmentData, getEnvironmentData } from "worker_threads";

// In main thread
setEnvironmentData("config", { apiUrl: "https://api.example.com" });

// In worker
const config = getEnvironmentData("config");
console.log(config); // => { apiUrl: "https://api.example.com" }

Worker 事件

使用 process.emit() 监听 worker 创建事件

process.on("worker", worker => {
  console.log("New worker created:", worker.threadId);
});

Bun.isMainThread

您可以通过检查 Bun.isMainThread 来判断您是否在主线程中。

if (Bun.isMainThread) {
  console.log("I'm the main thread");
} else {
  console.log("I'm in a worker");
}

这对于根据您是否在主线程上来有条件地运行代码很有用。