Skip to content

Worker te permite iniciar y comunicarte con una nueva instancia de JavaScript ejecutándose en un hilo separado mientras compartes recursos de I/O con el hilo principal.

Bun implementa una versión mínima de la API de Web Workers con extensiones que la hacen funcionar mejor para casos de uso del lado del servidor. Como el resto de Bun, Worker en Bun soporta CommonJS, Módulos ES, TypeScript, JSX, TSX y más sin configuración adicional. No son necesarios pasos de compilación adicionales.

Crear un Worker

Como en los navegadores, Worker es un global. Úsalo para crear un nuevo hilo de worker.

Desde el hilo principal

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

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

Hilo del worker

ts
// previene errores de TS
declare var self: Worker;

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

Para prevenir errores de TypeScript al usar self, agrega esta línea al principio de tu archivo de worker.

ts
declare var self: Worker;

Puedes usar la sintaxis import y export en tu código de worker. A diferencia de los navegadores, no es necesario especificar {type: "module"} para usar Módulos ES.

Para simplificar el manejo de errores, el script inicial para cargar se resuelve en el momento en que se llama a new Worker(url).

js
const worker = new Worker("/not-found.js");
// lanza un error inmediatamente

El especificador pasado a Worker se resuelve relativo a la raíz del proyecto (como escribir bun ./path/to/file.js).

preload - cargar módulos antes de que el worker inicie

Puedes pasar un array de especificadores de módulos a la opción preload para cargar módulos antes de que el worker inicie. Esto es útil cuando quieres asegurar que algún código siempre se cargue antes de que la aplicación inicie, como cargar OpenTelemetry, Sentry, DataDog, etc.

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

Como el argumento del CLI --preload, la opción preload se procesa antes de que el worker inicie.

También puedes pasar una sola cadena a la opción preload:

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

URLs blob:

También puedes pasar una URL blob: a Worker. Esto es útil para crear workers desde cadenas u otras fuentes.

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);

Como el resto de Bun, los workers creados desde URLs blob: soportan TypeScript, JSX y otros tipos de archivos sin configuración adicional. Puedes comunicar que debe cargarse vía typescript ya sea vía type o pasando un filename al constructor File.

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"

El evento "open" se emite cuando un worker se crea y está listo para recibir mensajes. Esto se puede usar para enviar un mensaje inicial a un worker una vez que está listo. (Este evento no existe en los navegadores.)

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

worker.addEventListener("open", () => {
  console.log("el worker está listo");
});

Los mensajes se encolan automáticamente hasta que el worker está listo, así que no es necesario esperar el evento "open" para enviar mensajes.

Mensajes con postMessage

Para enviar mensajes, usa worker.postMessage y self.postMessage. Esto aprovecha el Algoritmo de Clonado Estructurado HTML.

Optimizaciones de rendimiento

Bun incluye rutas rápidas optimizadas para postMessage para mejorar dramáticamente el rendimiento para tipos de datos comunes:

Ruta rápida de cadena - Al publicar valores de cadena puros, Bun omite completamente el algoritmo de clonado estructurado, logrando ganancias de rendimiento significativas sin sobrecarga de serialización.

Ruta rápida de objeto simple - Para objetos planos que contienen solo valores primitivos (cadenas, números, booleanos, null, undefined), Bun usa una ruta de serialización optimizada que almacena propiedades directamente sin clonado estructurado completo.

La ruta rápida de objeto simple se activa cuando el objeto:

  • Es un objeto plano sin modificaciones en la cadena de prototipos
  • Contiene solo propiedades de datos enumerables y configurables
  • No tiene propiedades indexadas o métodos getter/setter
  • Todos los valores de propiedad son primitivos o cadenas

Con estas rutas rápidas, el postMessage de Bun funciona 2-241x más rápido porque la longitud del mensaje ya no tiene un impacto significativo en el rendimiento.

Bun (con rutas rápidas):

ts
postMessage({ prop: cadena de 11 chars, ...9 props más }) - 648ns
postMessage({ prop: cadena de 14 KB, ...9 props más })    - 719ns
postMessage({ prop: cadena de 3 MB, ...9 props más })     - 1.26µs

Node.js v24.6.0 (para comparación):

js
postMessage({ prop: cadena de 11 chars, ...9 props más }) - 1.19µs
postMessage({ prop: cadena de 14 KB, ...9 props más })    - 2.69µs
postMessage({ prop: cadena de 3 MB, ...9 props más })     - 304µs
js
// Ruta rápida de cadena - optimizado
postMessage("¡Hola, worker!");

// Ruta rápida de objeto simple - optimizado
postMessage({
  mensaje: "Hola",
  cuenta: 42,
  habilitado: true,
  datos: null,
});

// Los objetos complejos aún funcionan pero usan clonado estructurado estándar
postMessage({
  anidado: { profundo: { objeto: true } },
  fecha: new Date(),
  buffer: new ArrayBuffer(8),
});
js
// En el hilo del worker, `postMessage` se "enruta" automáticamente al hilo padre.
postMessage({ hello: "world" });

// En el hilo principal
worker.postMessage({ hello: "world" });

Para recibir mensajes, usa el manejador de eventos message en el worker y el hilo principal.

js
// Hilo del worker:
self.addEventListener("message", event => {
  console.log(event.data);
});
// o usa el setter:
// self.onmessage = fn

// si está en el hilo principal
worker.addEventListener("message", event => {
  console.log(event.data);
});
// o usa el setter:
// worker.onmessage = fn

Terminar un worker

Una instancia Worker termina automáticamente una vez que su bucle de eventos no tiene más trabajo por hacer. Adjuntar un listener "message" en el global o cualquier MessagePort mantendrá el bucle de eventos activo. Para terminar forzosamente un Worker, llama a worker.terminate().

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

// ...algo de tiempo después
worker.terminate();

Esto hará que el worker termine lo antes posible.

process.exit()

Un worker puede terminarse a sí mismo con process.exit(). Esto no termina el proceso principal. Como en Node.js, process.on('beforeExit', callback) y process.on('exit', callback) se emiten en el hilo del worker (y no en el hilo principal), y el código de salida se pasa al evento "close".

"close"

El evento "close" se emite cuando un worker ha sido terminado. Puede tomar algo de tiempo para que el worker realmente termine, así que este evento se emite cuando el worker ha sido marcado como terminado. El CloseEvent contendrá el código de salida pasado a process.exit(), o 0 si se cerró por otras razones.

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

worker.addEventListener("close", event => {
  console.log("el worker se está cerrando");
});

Este evento no existe en los navegadores.

Gestionar el tiempo de vida

Por defecto, un Worker activo mantendrá vivo el proceso principal (generador), así que las tareas asíncronas como setTimeout y las promesas mantendrán el proceso vivo. Adjuntar listeners message también mantendrá el Worker vivo.

worker.unref()

Para evitar que un worker en ejecución mantenga vivo el proceso, llama a worker.unref(). Esto desacopla el tiempo de vida del worker del tiempo de vida del proceso principal y es equivalente a lo que hace worker_threads de Node.js.

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

Nota: worker.unref() no está disponible en los navegadores.

worker.ref()

Para mantener el proceso vivo hasta que el Worker termine, llama a worker.ref(). Un worker con ref es el comportamiento predeterminado y aún necesita que algo esté ocurriendo en el bucle de eventos (como un listener "message") para que el worker continúe ejecutándose.

ts
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();
// más tarde...
worker.ref();

Alternativamente, también puedes pasar un objeto options a Worker:

ts
const worker = new Worker(new URL("worker.ts", import.meta.url).href, {
  ref: false,
});

Nota: worker.ref() no está disponible en los navegadores.

Uso de memoria con smol

Las instancias de JavaScript pueden usar mucha memoria. Worker de Bun soporta un modo smol que reduce el uso de memoria, a costa del rendimiento. Para habilitar el modo smol, pasa smol: true al objeto options en el constructor Worker.

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

¿Qué hace realmente el modo smol?

Establecer smol: true establece JSC::HeapSize en Small en lugar del predeterminado Large.

Datos de Entorno

Comparte datos entre el hilo principal y los workers usando setEnvironmentData() y getEnvironmentData().

index.ts
ts
import { setEnvironmentData, getEnvironmentData } from "worker_threads";

// En el hilo principal
setEnvironmentData("config", { apiUrl: "https://api.example.com" });

// En el worker
const config = getEnvironmentData("config");
console.log(config); // => { apiUrl: "https://api.example.com" }

Eventos de Worker

Escucha eventos de creación de workers usando process.emit():

index.ts
ts
process.on("worker", worker => {
  console.log("Nuevo worker creado:", worker.threadId);
});

Bun.isMainThread

Puedes verificar si estás en el hilo principal verificando Bun.isMainThread.

ts
if (Bun.isMainThread) {
  console.log("Soy el hilo principal");
} else {
  console.log("Estoy en un worker");
}

Esto es útil para ejecutar código condicionalmente basado en si estás en el hilo principal o no.

Bun por www.bunjs.com.cn editar