Worker ermöglicht es Ihnen, eine neue JavaScript-Instanz auf einem separaten Thread zu starten und damit zu kommunizieren, während Sie I/O-Ressourcen mit dem Hauptthread teilen.
Bun implementiert eine minimale Version der Web Workers API mit Erweiterungen, die sie für serverseitige Anwendungsfälle besser geeignet machen. Wie der Rest von Bun unterstützt Worker in Bun CommonJS, ES-Module, TypeScript, JSX, TSX und mehr out of the box. Keine zusätzlichen Build-Schritte erforderlich.
Erstellen eines Worker
Wie in Browsern ist Worker ein globales Objekt. Verwenden Sie es, um einen neuen Worker-Thread zu erstellen.
Vom Hauptthread aus
const worker = new Worker("./worker.ts");
worker.postMessage("hello");
worker.onmessage = event => {
console.log(event.data);
};Worker-Thread
// verhindert TS-Fehler
declare var self: Worker;
self.onmessage = (event: MessageEvent) => {
console.log(event.data);
postMessage("world");
};Um TypeScript-Fehler bei der Verwendung von self zu vermeiden, fügen Sie diese Zeile oben in Ihre Worker-Datei ein.
declare var self: Worker;Sie können import- und export-Syntax in Ihrem Worker-Code verwenden. Im Gegensatz zu Browsern müssen Sie nicht {type: "module"} angeben, um ES-Module zu verwenden.
Um die Fehlerbehandlung zu vereinfachen, wird das anfänglich zu ladende Skript zum Zeitpunkt des Aufrufs von new Worker(url) aufgelöst.
const worker = new Worker("/not-found.js");
// wirft sofort einen FehlerDer an Worker übergebene Spezifizierer wird relativ zum Projektstamm aufgelöst (wie bei der Eingabe von bun ./path/to/file.js).
preload - Module laden, bevor der Worker startet
Sie können ein Array von Modulspezifizierern an die preload-Option übergeben, um Module zu laden, bevor der Worker startet. Dies ist nützlich, wenn Sie sicherstellen möchten, dass bestimmter Code immer geladen wird, bevor die Anwendung startet, wie z. B. OpenTelemetry, Sentry, DataDog usw.
const worker = new Worker("./worker.ts", {
preload: ["./load-sentry.js"],
});Wie das --preload-CLI-Argument wird die preload-Option verarbeitet, bevor der Worker startet.
Sie können auch einen einzelnen String an die preload-Option übergeben:
const worker = new Worker("./worker.ts", {
preload: "./load-sentry.js",
});blob: URLs
Sie können auch eine blob:-URL an Worker übergeben. Dies ist nützlich, um Worker aus Strings oder anderen Quellen zu erstellen.
const blob = new Blob([`self.onmessage = (event: MessageEvent) => postMessage(event.data)`], {
type: "application/typescript",
});
const url = URL.createObjectURL(blob);
const worker = new Worker(url);Wie der Rest von Bun unterstützen Worker, die aus blob:-URLs erstellt werden, TypeScript, JSX und andere Dateitypen out of the box. Sie können mitteilen, dass es über TypeScript geladen werden soll, entweder über type oder durch Übergeben eines filename an den File-Konstruktor.
const file = new File([`self.onmessage = (event: MessageEvent) => postMessage(event.data)`], "worker.ts");
const url = URL.createObjectURL(file);
const worker = new Worker(url);"open"
Das "open"-Ereignis wird ausgelöst, wenn ein Worker erstellt wird und bereit ist, Nachrichten zu empfangen. Dies kann verwendet werden, um eine initiale Nachricht an einen Worker zu senden, sobald er bereit ist. (Dieses Ereignis existiert in Browsern nicht.)
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.addEventListener("open", () => {
console.log("worker is ready");
});Nachrichten werden automatisch in die Warteschlange gestellt, bis der Worker bereit ist, sodass es nicht notwendig ist, auf das "open"-Ereignis zu warten, um Nachrichten zu senden.
Nachrichten mit postMessage
Um Nachrichten zu senden, verwenden Sie worker.postMessage und self.postMessage. Dies nutzt den HTML Structured Clone Algorithm.
Leistungsoptimierungen
Bun enthält optimierte Fast Paths für postMessage, um die Leistung für gängige Datentypen dramatisch zu verbessern:
String-Fast-Path - Beim Posten von reinen String-Werten umgeht Bun den Structured-Clone-Algorithmus vollständig und erzielt erhebliche Leistungsgewinne ohne Serialisierungs-Overhead.
Einfacher Objekt-Fast-Path - Für einfache Objekte, die nur primitive Werte enthalten (Strings, Zahlen, Booleans, null, undefined), verwendet Bun einen optimierten Serialisierungspfad, der Eigenschaften direkt speichert, ohne vollständiges strukturiertes Klonen.
Der einfache Objekt-Fast-Path wird aktiviert, wenn das Objekt:
- Ein einfaches Objekt ohne Prototypketten-Modifikationen ist
- Nur aufzählbare, konfigurierbare Dateneigenschaften enthält
- Keine indizierten Eigenschaften oder Getter/Setter-Methoden hat
- Alle Eigenschaftswerte Primitive oder Strings sind
Mit diesen Fast Paths führt Buns postMessage 2-241x schneller aus, da die Nachrichtenlänge keine bedeutende Auswirkung mehr auf die Leistung hat.
Bun (mit Fast Paths):
postMessage({ prop: 11 chars string, ...9 more props }) - 648ns
postMessage({ prop: 14 KB string, ...9 more props }) - 719ns
postMessage({ prop: 3 MB string, ...9 more props }) - 1.26µsNode.js v24.6.0 (zum Vergleich):
postMessage({ prop: 11 chars string, ...9 more props }) - 1.19µs
postMessage({ prop: 14 KB string, ...9 more props }) - 2.69µs
postMessage({ prop: 3 MB string, ...9 more props }) - 304µs// String fast path - optimiert
postMessage("Hello, worker!");
// Simple object fast path - optimiert
postMessage({
message: "Hello",
count: 42,
enabled: true,
data: null,
});
// Komplexe Objekte funktionieren weiterhin, verwenden aber Standard-Structured-Clone
postMessage({
nested: { deep: { object: true } },
date: new Date(),
buffer: new ArrayBuffer(8),
});// Auf dem Worker-Thread wird `postMessage` automatisch an den übergeordneten Thread "weitergeleitet".
postMessage({ hello: "world" });
// Auf dem Hauptthread
worker.postMessage({ hello: "world" });Um Nachrichten zu empfangen, verwenden Sie den message-Event-Handler auf dem Worker- und Hauptthread.
// Worker-Thread:
self.addEventListener("message", event => {
console.log(event.data);
});
// oder verwenden Sie den Setter:
// self.onmessage = fn
// wenn auf dem Hauptthread
worker.addEventListener("message", event => {
console.log(event.data);
});
// oder verwenden Sie den Setter:
// worker.onmessage = fnBeenden eines Workers
Eine Worker-Instanz beendet sich automatisch, sobald ihre Event-Loop keine Arbeit mehr zu erledigen hat. Das Anhängen eines "message"-Listeners am globalen Objekt oder an MessagePorts hält die Event-Loop am Leben. Um einen Worker gewaltsam zu beenden, rufen Sie worker.terminate() auf.
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
// ... einige Zeit später
worker.terminate();Dies bewirkt, dass der Worker so schnell wie möglich beendet wird.
process.exit()
Ein Worker kann sich selbst mit process.exit() beenden. Dies beendet nicht den Hauptprozess. Wie in Node.js werden process.on('beforeExit', callback) und process.on('exit', callback) auf dem Worker-Thread (und nicht auf dem Hauptthread) ausgelöst, und der Exit-Code wird an das "close"-Ereignis übergeben.
"close"
Das "close"-Ereignis wird ausgelöst, wenn ein Worker beendet wurde. Es kann einige Zeit dauern, bis der Worker tatsächlich beendet ist, daher wird dieses Ereignis ausgelöst, wenn der Worker als beendet markiert wurde. Das CloseEvent enthält den an process.exit() übergebenen Exit-Code oder 0, wenn er aus anderen Gründen geschlossen wurde.
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.addEventListener("close", event => {
console.log("worker is being closed");
});Dieses Ereignis existiert in Browsern nicht.
Lebensdauer verwalten
Standardmäßig hält ein aktiver Worker den Hauptprozess (der ihn erstellt hat) am Leben, sodass asynchrone Aufgaben wie setTimeout und Promises den Prozess am Leben halten. Das Anhängen von message-Listeners hält den Worker ebenfalls am Leben.
worker.unref()
Um zu verhindern, dass ein laufender Worker den Prozess am Leben hält, rufen Sie worker.unref() auf. Dies entkoppelt die Lebensdauer des Workers von der Lebensdauer des Hauptprozesses und entspricht dem, was Node.js' worker_threads tut.
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();Hinweis: worker.unref() ist in Browsern nicht verfügbar.
worker.ref()
Um den Prozess am Leben zu halten, bis der Worker beendet wird, rufen Sie worker.ref() auf. Ein referenzierter Worker ist das Standardverhalten und benötigt weiterhin etwas, das in der Event-Loop passiert (wie einen "message"-Listener), damit der Worker weiterläuft.
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();
// später...
worker.ref();Alternativ können Sie auch ein options-Objekt an Worker übergeben:
const worker = new Worker(new URL("worker.ts", import.meta.url).href, {
ref: false,
});Hinweis: worker.ref() ist in Browsern nicht verfügbar.
Speichernutzung mit smol
JavaScript-Instanzen können viel Speicher verwenden. Buns Worker unterstützt einen smol-Modus, der die Speichernutzung auf Kosten der Leistung reduziert. Um den smol-Modus zu aktivieren, übergeben Sie smol: true an das options-Objekt im Worker-Konstruktor.
const worker = new Worker("./i-am-smol.ts", {
smol: true,
});Was macht der smol-Modus eigentlich?">
Das Setzen von smol: true setzt JSC::HeapSize auf Small anstelle des Standardwerts Large.
Umgebungsdaten
Teilen Sie Daten zwischen dem Hauptthread und Workern mit setEnvironmentData() und getEnvironmentData().
import { setEnvironmentData, getEnvironmentData } from "worker_threads";
// Im Hauptthread
setEnvironmentData("config", { apiUrl: "https://api.example.com" });
// Im Worker
const config = getEnvironmentData("config");
console.log(config); // => { apiUrl: "https://api.example.com" }Worker-Ereignisse
Hören Sie auf Worker-Erstellungsereignisse mit process.emit():
process.on("worker", worker => {
console.log("New worker created:", worker.threadId);
});Bun.isMainThread
Sie können überprüfen, ob Sie sich im Hauptthread befinden, indem Sie Bun.isMainThread überprüfen.
if (Bun.isMainThread) {
console.log("I'm the main thread");
} else {
console.log("I'm in a worker");
}Dies ist nützlich, um Code bedingt auszuführen, je nachdem, ob Sie sich im Hauptthread befinden oder nicht.