Worker 让你可以启动并与运行在单独线程上的新 JavaScript 实例通信,同时与主线程共享 I/O 资源。
Bun 实现了 Web Workers API 的最小版本,并进行了扩展,使其更好地适用于服务器端用例。与 Bun 的其余部分一样,Bun 中的 Worker 开箱即用地支持 CommonJS、ES Modules、TypeScript、JSX、TSX 等。不需要额外的构建步骤。
创建 Worker
与在浏览器中一样,Worker 是一个全局变量。使用它创建新的 worker 线程。
从主线程
const worker = new Worker("./worker.ts");
worker.postMessage("hello");
worker.onmessage = event => {
console.log(event.data);
};Worker 线程
// 防止 TS 错误
declare var self: Worker;
self.onmessage = (event: MessageEvent) => {
console.log(event.data);
postMessage("world");
};要在使用 self 时防止 TypeScript 错误,在 worker 文件顶部添加这行。
declare var self: Worker;你可以在 worker 代码中使用 import 和 export 语法。与在浏览器中不同,不需要指定 {type: "module"} 来使用 ES Modules。
为简化错误处理,初始加载的脚本在调用 new Worker(url) 时解析。
const worker = new Worker("/not-found.js");
// 立即抛出错误传递给 Worker 的标识符相对于项目根目录解析(就像输入 bun ./path/to/file.js)。
preload - 在 worker 启动前加载模块
你可以传递模块标识符数组给 preload 选项,在 worker 启动前加载模块。当你想确保某些代码在应用程序启动前始终加载时很有用,比如加载 OpenTelemetry、Sentry、DataDog 等。
const worker = new Worker("./worker.ts", {
preload: ["./load-sentry.js"],
});像 --preload CLI 参数一样,preload 选项在 worker 启动前处理。
你也可以传递单个字符串给 preload 选项:
const worker = new Worker("./worker.ts", {
preload: "./load-sentry.js",
});blob: URL
你也可以传递 blob: URL 给 Worker。这对于从字符串或其他源创建 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 创建的 worker 开箱即用地支持 TypeScript、JSX 和其他文件类型。你可以通过 type 或通过传递 filename 给 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 准备好后向其发送初始消息。(此事件在浏览器中不存在。)
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.addEventListener("open", () => {
console.log("worker is ready");
});消息会自动入队直到 worker 准备好,所以不需要等待 "open" 事件来发送消息。
使用 postMessage 发送消息
要发送消息,使用 worker.postMessage 和 self.postMessage。这利用了 HTML 结构化克隆算法。
性能优化
Bun 包含优化的快速路径 postMessage,以显著提高常见数据类型的性能:
字符串快速路径 - 当发布纯字符串值时,Bun 完全绕过结构化克隆算法,实现显著的性能提升,没有序列化开销。
简单对象快速路径 - 对于仅包含原始值(字符串、数字、布尔值、null、undefined)的普通对象,Bun 使用优化的序列化路径直接存储属性,无需完全结构化克隆。
简单对象快速路径在对象满足以下条件时激活:
- 是没有原型链修改的普通对象
- 仅包含可枚举、可配置的数据属性
- 没有索引属性或 getter/setter 方法
- 所有属性值都是原始值或字符串
使用这些快速路径,Bun 的 postMessage 性能快 2-241 倍,因为消息长度不再对性能产生有意义的影响。
Bun(使用快速路径):
postMessage({ prop: 11 字符字符串,...9 更多属性 }) - 648ns
postMessage({ prop: 14 KB 字符串,...9 更多属性 }) - 719ns
postMessage({ prop: 3 MB 字符串,...9 更多属性 }) - 1.26µsNode.js v24.6.0(供比较):
postMessage({ prop: 11 字符字符串,...9 更多属性 }) - 1.19µs
postMessage({ prop: 14 KB 字符串,...9 更多属性 }) - 2.69µs
postMessage({ prop: 3 MB 字符串,...9 更多属性 }) - 304µs// 字符串快速路径 - 优化
postMessage("Hello, worker!");
// 简单对象快速路径 - 优化
postMessage({
message: "Hello",
count: 42,
enabled: true,
data: null,
});
// 复杂对象仍然有效但使用标准结构化克隆
postMessage({
nested: { deep: { object: true } },
date: new Date(),
buffer: new ArrayBuffer(8),
});// 在 worker 线程上,`postMessage` 自动"路由"到父线程。
postMessage({ hello: "world" });
// 在主线程上
worker.postMessage({ hello: "world" });要接收消息,在 worker 和主线程上使用 message 事件处理程序。
// Worker 线程:
self.addEventListener("message", event => {
console.log(event.data);
});
// 或使用 setter:
// self.onmessage = fn
// 如果在主线程上
worker.addEventListener("message", event => {
console.log(event.data);
});
// 或使用 setter:
// worker.onmessage = fn终止 worker
一旦 Worker 实例的事件循环没有工作要做,它会自动终止。在全局或任何 MessagePort 上附加 "message" 监听器将使事件循环保持活动状态。要强制终止 Worker,调用 worker.terminate()。
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
// ...一段时间后
worker.terminate();这将导致 worker 尽快退出。
process.exit()
worker 可以使用 process.exit() 终止自己。这不会终止主进程。与在 Node.js 中一样,process.on('beforeExit', callback) 和 process.on('exit', callback) 在 worker 线程上发出(不在主线程上),退出代码传递给 "close" 事件。
"close"
当 worker 被终止时,发出 "close" 事件。worker 实际终止可能需要一些时间,所以当 worker 被标记为终止时发出此事件。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 和 promise 这样的异步任务将使进程保持活动状态。附加 message 监听器也将使 Worker 保持活动状态。
worker.unref()
要停止运行中的 worker 使进程保持活动状态,调用 worker.unref()。这将 worker 的生命周期与主进程的生命周期解耦,等同于 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" 监听器)才能使 worker 继续运行。
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();
// 稍后...
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 构造函数的 options 对象中传递 smol: true。
const worker = new Worker("./i-am-smol.ts", {
smol: true,
});smol 模式实际上做了什么?">
设置 smol: true 将 JSC::HeapSize 设置为 Small 而不是默认的 Large。
环境数据
使用 setEnvironmentData() 和 getEnvironmentData() 在主线程和 worker 之间共享数据。
import { setEnvironmentData, getEnvironmentData } from "worker_threads";
// 在主线程中
setEnvironmentData("config", { apiUrl: "https://api.example.com" });
// 在 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");
}这对于根据你是否在主线程中有条件地运行代码很有用。