Bun

工作執行緒

🚧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 錯誤,請將此行新增至您的 worker 檔案頂部。

declare var self: Worker;

您可以在您的 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 或將 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"

當工作執行緒建立並準備好接收訊息時,會發出 "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 結構化複製演算法

// 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 和 Promise 等非同步任務將保持程序處於活動狀態。附加 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 的工作執行緒是預設行為,並且仍然需要在事件迴圈中進行某些操作(例如 "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 模式,請將 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");
}

這對於根據您是否在主執行緒中條件式地執行程式碼非常有用。