Worker ti permette di avviare e comunicare con una nuova istanza JavaScript in esecuzione su un thread separato mentre condivide le risorse I/O con il thread principale.
Bun implementa una versione minima dell'API Web Workers con estensioni che la rendono più adatta per casi d'uso lato server. Come il resto di Bun, Worker in Bun supporta CommonJS, ES Modules, TypeScript, JSX, TSX e altro out of the box. Non sono necessari passaggi di build extra.
Creare un Worker
Come nei browser, Worker è un globale. Usalo per creare un nuovo thread worker.
Dal thread principale
const worker = new Worker("./worker.ts");
worker.postMessage("hello");
worker.onmessage = event => {
console.log(event.data);
};Thread worker
// previene errori TS
declare var self: Worker;
self.onmessage = (event: MessageEvent) => {
console.log(event.data);
postMessage("world");
};Per prevenire errori TypeScript quando usi self, aggiungi questa riga in cima al tuo file worker.
declare var self: Worker;Puoi usare la sintassi import e export nel tuo codice worker. A differenza dei browser, non c'è bisogno di specificare {type: "module"} per usare gli ES Modules.
Per semplificare la gestione degli errori, lo script iniziale da caricare viene risolto al momento in cui viene chiamato new Worker(url).
const worker = new Worker("/not-found.js");
// lancia un errore immediatamenteLo specifier passato a Worker viene risolto relativamente alla root del progetto (come digitare bun ./path/to/file.js).
preload - carica moduli prima che il worker inizi
Puoi passare un array di specifier di moduli all'opzione preload per caricare moduli prima che il worker inizi. Questo è utile quando vuoi assicurarti che del codice sia sempre caricato prima che l'applicazione inizi, come caricare OpenTelemetry, Sentry, DataDog, ecc.
const worker = new Worker("./worker.ts", {
preload: ["./load-sentry.js"],
});Come l'argomento CLI --preload, l'opzione preload viene processata prima che il worker inizi.
Puoi anche passare una singola stringa all'opzione preload:
const worker = new Worker("./worker.ts", {
preload: "./load-sentry.js",
});URL blob:
Puoi anche passare un URL blob: a Worker. Questo è utile per creare worker da stringhe o altre fonti.
const blob = new Blob([`self.onmessage = (event: MessageEvent) => postMessage(event.data)`], {
type: "application/typescript",
});
const url = URL.createObjectURL(blob);
const worker = new Worker(url);Come il resto di Bun, i worker creati da URL blob: supportano TypeScript, JSX e altri tipi di file out of the box. Puoi comunicare che dovrebbe essere caricato via typescript sia tramite type che passando un filename al costruttore di File.
const file = new File([`self.onmessage = (event: MessageEvent) => postMessage(event.data)`], "worker.ts");
const url = URL.createObjectURL(file);
const worker = new Worker(url);"open"
L'evento "open" viene emesso quando un worker viene creato e pronto a ricevere messaggi. Questo può essere usato per inviare un messaggio iniziale a un worker una volta che è pronto. (Questo evento non esiste nei browser.)
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.addEventListener("open", () => {
console.log("worker is ready");
});I messaggi vengono automaticamente accodati fino a quando il worker è pronto, quindi non c'è bisogno di aspettare l'evento "open" per inviare messaggi.
Messaggi con postMessage
Per inviare messaggi, usa worker.postMessage e self.postMessage. Questo sfrutta l'Algoritmo di Clonazione Strutturata HTML.
Ottimizzazioni delle prestazioni
Bun include percorsi veloci ottimizzati per postMessage per migliorare drasticamente le prestazioni per i tipi di dati comuni:
Percorso veloce per stringhe - Quando si inviano valori stringa puri, Bun bypassa completamente l'algoritmo di clonazione strutturata, ottenendo significativi guadagni di prestazioni senza overhead di serializzazione.
Percorso veloce per oggetti semplici - Per oggetti semplici contenenti solo valori primitivi (stringhe, numeri, booleani, null, undefined), Bun usa un percorso di serializzazione ottimizzato che memorizza le proprietà direttamente senza clonazione strutturata completa.
Il percorso veloce per oggetti semplici si attiva quando l'oggetto:
- È un oggetto semplice senza modifiche alla catena del prototipo
- Contiene solo proprietà di dati enumerabili e configurabili
- Non ha proprietà indicizzate o metodi getter/setter
- Tutti i valori delle proprietà sono primitivi o stringhe
Con questi percorsi veloci, il postMessage di Bun funziona 2-241 volte più velocemente perché la lunghezza del messaggio non ha più un impatto significativo sulle prestazioni.
Bun (con percorsi veloci):
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 (per confronto):
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// Percorso veloce per stringhe - ottimizzato
postMessage("Hello, worker!");
// Percorso veloce per oggetti semplici - ottimizzato
postMessage({
message: "Hello",
count: 42,
enabled: true,
data: null,
});
// Gli oggetti complessi funzionano ancora ma usano la clonazione strutturata standard
postMessage({
nested: { deep: { object: true } },
date: new Date(),
buffer: new ArrayBuffer(8),
});// Nel thread worker, `postMessage` viene automaticamente "instradato" al thread padre.
postMessage({ hello: "world" });
// Nel thread principale
worker.postMessage({ hello: "world" });Per ricevere messaggi, usa il gestore dell'evento message sul worker e sul thread principale.
// Thread worker:
self.addEventListener("message", event => {
console.log(event.data);
});
// oppure usa il setter:
// self.onmessage = fn
// se sul thread principale
worker.addEventListener("message", event => {
console.log(event.data);
});
// oppure usa il setter:
// worker.onmessage = fnTerminare un worker
Un'istanza Worker termina automaticamente una volta che il suo event loop non ha più lavoro da fare. Allegare un listener "message" al globale o a qualsiasi MessagePort terrà vivo l'event loop. Per terminare forzatamente un Worker, chiama worker.terminate().
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
// ...dopo un po'
worker.terminate();Questo farà uscire il worker il prima possibile.
process.exit()
Un worker può terminare se stesso con process.exit(). Questo non termina il processo principale. Come in Node.js, process.on('beforeExit', callback) e process.on('exit', callback) vengono emessi sul thread worker (e non sul thread principale), e il codice di uscita viene passato all'evento "close".
"close"
L'evento "close" viene emesso quando un worker è stato terminato. Può richiedere del tempo perché il worker termini effettivamente, quindi questo evento viene emesso quando il worker è stato contrassegnato come terminato. Il CloseEvent conterrà il codice di uscita passato a process.exit(), o 0 se chiuso per altri motivi.
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.addEventListener("close", event => {
console.log("worker is being closed");
});Questo evento non esiste nei browser.
Gestione del ciclo di vita
Per impostazione predefinita, un Worker attivo terrà vivo il processo principale (che ha generato il worker), quindi attività asincrone come setTimeout e promise terranno vivo il processo. Allegare listener message terrà anche vivo il Worker.
worker.unref()
Per impedire a un worker in esecuzione di tenere vivo il processo, chiama worker.unref(). Questo disaccoppia il ciclo di vita del worker dal ciclo di vita del processo principale, ed è equivalente a ciò che fa worker_threads di Node.js.
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();Nota: worker.unref() non è disponibile nei browser.
worker.ref()
Per mantenere il processo vivo fino a quando il Worker termina, chiama worker.ref(). Un worker referenziato è il comportamento predefinito, e ha ancora bisogno di qualcosa in corso nell'event loop (come un listener "message") per continuare a funzionare.
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();
// dopo...
worker.ref();In alternativa, puoi anche passare un oggetto options a Worker:
const worker = new Worker(new URL("worker.ts", import.meta.url).href, {
ref: false,
});Nota: worker.ref() non è disponibile nei browser.
Utilizzo della memoria con smol
Le istanze JavaScript possono usare molta memoria. Il Worker di Bun supporta una modalità smol che riduce l'utilizzo della memoria, al costo delle prestazioni. Per abilitare la modalità smol, passa smol: true all'oggetto options nel costruttore di Worker.
const worker = new Worker("./i-am-smol.ts", {
smol: true,
});Cosa fa effettivamente la modalità smol">
Impostare smol: true imposta JSC::HeapSize su Small invece del valore predefinito Large.
Dati ambientali
Condividi dati tra il thread principale e i worker usando setEnvironmentData() e getEnvironmentData().
import { setEnvironmentData, getEnvironmentData } from "worker_threads";
// Nel thread principale
setEnvironmentData("config", { apiUrl: "https://api.example.com" });
// Nel worker
const config = getEnvironmentData("config");
console.log(config); // => { apiUrl: "https://api.example.com" }Eventi del Worker
Ascolta gli eventi di creazione del worker usando process.emit():
process.on("worker", worker => {
console.log("New worker created:", worker.threadId);
});Bun.isMainThread
Puoi verificare se sei nel thread principale controllando Bun.isMainThread.
if (Bun.isMainThread) {
console.log("I'm the main thread");
} else {
console.log("I'm in a worker");
}Questo è utile per eseguire condizionalmente il codice in base a se sei nel thread principale o no.