🚧 — Worker
API 仍处于实验阶段,不应被视为已准备好用于生产环境。
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 线程
// prevents TS errors
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");
// throws an error immediately
传递给 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",
});
此功能在 Bun v1.1.35 版本中添加。
blob:
URL
从 Bun v1.1.13 版本开始,您还可以将 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 结构化克隆算法。
// On the worker thread, `postMessage` is automatically "routed" to the parent thread.
postMessage({ hello: "world" });
// On the main thread
worker.postMessage({ hello: "world" });
要接收消息,请在 worker 和主线程上使用 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
一旦 Worker
实例的事件循环没有剩余工作要做,它将自动终止。在全球作用域或任何 MessagePort
上附加 "message"
监听器将保持事件循环处于活动状态。要强制终止 Worker
,请调用 worker.terminate()
。
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
// ...some time later
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
和 promises 这样的异步任务将保持进程处于活动状态。附加 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 的 worker 是默认行为,并且仍然需要在事件循环中进行一些操作(例如 "message"
监听器)才能使 worker 继续运行。
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
模式,请将 smol: true
传递给 Worker
构造函数中的 options
对象。
const worker = new Worker("./i-am-smol.ts", {
smol: true,
});
smol
模式实际上有什么作用?
Bun.isMainThread
您可以通过检查 Bun.isMainThread
来检查您是否在主线程中。
if (Bun.isMainThread) {
console.log("I'm the main thread");
} else {
console.log("I'm in a worker");
}
这对于根据您是否在主线程中来有条件地运行代码非常有用。