Skip to content

Worker 讓你可以啟動並與運行在單獨線程上的新 JavaScript 實例通信,同時與主線程共享 I/O 資源。

Bun 實現了 Web Workers API 的最小版本,並進行了擴展,使其更好地適用於服務器端用例。與 Bun 的其余部分一樣,Bun 中的 Worker 開箱即用地支持 CommonJS、ES Modules、TypeScript、JSX、TSX 等。不需要額外的構建步驟。

創建 Worker

與在瀏覽器中一樣,Worker 是一個全局變量。使用它創建新的 worker 線程。

從主線程

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

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

Worker 線程

ts
// 防止 TS 錯誤
declare var self: Worker;

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

要在使用 self 時防止 TypeScript 錯誤,在 worker 文件頂部添加這行。

ts
declare var self: Worker;

你可以在 worker 代碼中使用 importexport 語法。與在瀏覽器中不同,不需要指定 {type: "module"} 來使用 ES Modules。

為簡化錯誤處理,初始加載的腳本在調用 new Worker(url) 時解析。

js
const worker = new Worker("/not-found.js");
// 立即拋出錯誤

傳遞給 Worker 的標識符相對於項目根目錄解析(就像輸入 bun ./path/to/file.js)。

preload - 在 worker 啟動前加載模塊

你可以傳遞模塊標識符數組給 preload 選項,在 worker 啟動前加載模塊。當你想確保某些代碼在應用程序啟動前始終加載時很有用,比如加載 OpenTelemetry、Sentry、DataDog 等。

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

--preload CLI 參數一樣,preload 選項在 worker 啟動前處理。

你也可以傳遞單個字符串給 preload 選項:

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

blob: URL

你也可以傳遞 blob: URL 給 Worker。這對於從字符串或其他源創建 worker 很有用。

js
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 或通過傳遞 filenameFile 構造函數來指示它應該通過 typescript 加載。

ts
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 准備好後向其發送初始消息。(此事件在瀏覽器中不存在。)

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

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

消息會自動入隊直到 worker 准備好,所以不需要等待 "open" 事件來發送消息。

使用 postMessage 發送消息

要發送消息,使用 worker.postMessageself.postMessage。這利用了 HTML 結構化克隆算法

性能優化

Bun 包含優化的快速路徑 postMessage,以顯著提高常見數據類型的性能:

字符串快速路徑 - 當發布純字符串值時,Bun 完全繞過結構化克隆算法,實現顯著的性能提升,沒有序列化開銷。

簡單對象快速路徑 - 對於僅包含原始值(字符串、數字、布爾值、null、undefined)的普通對象,Bun 使用優化的序列化路徑直接存儲屬性,無需完全結構化克隆。

簡單對象快速路徑在對象滿足以下條件時激活:

  • 是沒有原型鏈修改的普通對象
  • 僅包含可枚舉、可配置的數據屬性
  • 沒有索引屬性或 getter/setter 方法
  • 所有屬性值都是原始值或字符串

使用這些快速路徑,Bun 的 postMessage 性能快 2-241 倍,因為消息長度不再對性能產生有意義的影響。

Bun(使用快速路徑):

ts
postMessage({ prop: 11 字符字符串,...9 更多屬性 }) - 648ns
postMessage({ prop: 14 KB 字符串,...9 更多屬性 })    - 719ns
postMessage({ prop: 3 MB 字符串,...9 更多屬性 })     - 1.26µs

Node.js v24.6.0(供比較):

js
postMessage({ prop: 11 字符字符串,...9 更多屬性 }) - 1.19µs
postMessage({ prop: 14 KB 字符串,...9 更多屬性 })    - 2.69µs
postMessage({ prop: 3 MB 字符串,...9 更多屬性 })     - 304µs
js
// 字符串快速路徑 - 優化
postMessage("Hello, worker!");

// 簡單對象快速路徑 - 優化
postMessage({
  message: "Hello",
  count: 42,
  enabled: true,
  data: null,
});

// 復雜對象仍然有效但使用標准結構化克隆
postMessage({
  nested: { deep: { object: true } },
  date: new Date(),
  buffer: new ArrayBuffer(8),
});
js
// 在 worker 線程上,`postMessage` 自動"路由"到父線程。
postMessage({ hello: "world" });

// 在主線程上
worker.postMessage({ hello: "world" });

要接收消息,在 worker 和主線程上使用 message 事件處理程序

js
// 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()

ts
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。

ts
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 所做的。

ts
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 繼續運行。

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

或者,你也可以傳遞 options 對象給 Worker

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

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

smol 模式實際上做了什麼?">

設置 smol: trueJSC::HeapSize 設置為 Small 而不是默認的 Large

環境數據

使用 setEnvironmentData()getEnvironmentData() 在主線程和 worker 之間共享數據。

ts
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 創建事件:

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

Bun.isMainThread

你可以通過檢查 Bun.isMainThread 來檢查你是否在主線程中。

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

這對於根據你是否在主線程中有條件地運行代碼很有用。

Bun學習網由www.bunjs.com.cn整理維護