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");
}這對於根據你是否在主線程中有條件地運行代碼很有用。