Worker vous permet de démarrer et de communiquer avec une nouvelle instance JavaScript s'exécutant sur un thread séparé tout en partageant les ressources I/O avec le thread principal.
Bun implémente une version minimale de l'API Web Workers avec des extensions qui la font mieux fonctionner pour les cas d'utilisation côté serveur. Comme le reste de Bun, Worker dans Bun prend en charge CommonJS, ES Modules, TypeScript, JSX, TSX et plus encore nativement. Aucune étape de build supplémentaire n'est nécessaire.
Création d'un Worker
Comme dans les navigateurs, Worker est un global. Utilisez-le pour créer un nouveau thread worker.
Depuis le thread principal
const worker = new Worker("./worker.ts");
worker.postMessage("hello");
worker.onmessage = event => {
console.log(event.data);
};Thread worker
// évite les erreurs TS
declare var self: Worker;
self.onmessage = (event: MessageEvent) => {
console.log(event.data);
postMessage("world");
};Pour éviter les erreurs TypeScript lors de l'utilisation de self, ajoutez cette ligne en haut de votre fichier worker.
declare var self: Worker;Vous pouvez utiliser la syntaxe import et export dans votre code worker. Contrairement aux navigateurs, il n'est pas nécessaire de spécifier {type: "module"} pour utiliser ES Modules.
Pour simplifier la gestion des erreurs, le script initial à charger est résolu au moment où new Worker(url) est appelé.
const worker = new Worker("/not-found.js");
// génère une erreur immédiatementLe spécificateur passé à Worker est résolu par rapport à la racine du projet (comme taper bun ./path/to/file.js).
preload - charger des modules avant le démarrage du worker
Vous pouvez passer un tableau de spécificateurs de modules à l'option preload pour charger des modules avant le démarrage du worker. Cela est utile lorsque vous voulez vous assurer que du code est toujours chargé avant le démarrage de l'application, comme charger OpenTelemetry, Sentry, DataDog, etc.
const worker = new Worker("./worker.ts", {
preload: ["./load-sentry.js"],
});Comme l'argument CLI --preload, l'option preload est traitée avant le démarrage du worker.
Vous pouvez également passer une seule chaîne à l'option preload :
const worker = new Worker("./worker.ts", {
preload: "./load-sentry.js",
});URLs blob:
Vous pouvez également passer une URL blob: à Worker. Cela est utile pour créer des workers à partir de chaînes ou d'autres sources.
const blob = new Blob([`self.onmessage = (event: MessageEvent) => postMessage(event.data)`], {
type: "application/typescript",
});
const url = URL.createObjectURL(blob);
const worker = new Worker(url);Comme le reste de Bun, les workers créés à partir d'URL blob: prennent en charge TypeScript, JSX et d'autres types de fichiers nativement. Vous pouvez indiquer qu'il doit être chargé via typescript soit via type, soit en passant un filename au constructeur 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'événement "open" est émis lorsqu'un worker est créé et prêt à recevoir des messages. Cela peut être utilisé pour envoyer un message initial à un worker une fois qu'il est prêt. (Cet événement n'existe pas dans les navigateurs.)
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.addEventListener("open", () => {
console.log("worker is ready");
});Les messages sont automatiquement mis en file d'attente jusqu'à ce que le worker soit prêt, il n'est donc pas nécessaire d'attendre l'événement "open" pour envoyer des messages.
Messages avec postMessage
Pour envoyer des messages, utilisez worker.postMessage et self.postMessage. Cela exploite l'algorithme de clonage structuré HTML.
Optimisations de performance
Bun inclut des chemins rapides optimisés pour postMessage pour améliorer considérablement les performances pour les types de données courants :
Chemin rapide pour les chaînes - Lors de l'envoi de valeurs de type chaîne pure, Bun contourne complètement l'algorithme de clonage structuré, obtenant des gains de performance significatifs sans frais de sérialisation.
Chemin rapide pour les objets simples - Pour les objets simples contenant uniquement des valeurs primitives (chaînes, nombres, booléens, null, undefined), Bun utilise un chemin de sérialisation optimisé qui stocke les propriétés directement sans clonage structuré complet.
Le chemin rapide pour les objets simples s'active lorsque l'objet :
- Est un objet simple sans modifications de chaîne de prototype
- Contient uniquement des propriétés de données énumérables et configurables
- N'a pas de propriétés indexées ou de méthodes getter/setter
- Toutes les valeurs de propriété sont des primitives ou des chaînes
Avec ces chemins rapides, postMessage de Bun est 2 à 241 fois plus rapide car la longueur du message n'a plus d'impact significatif sur les performances.
Bun (avec chemins rapides) :
postMessage({ prop: chaîne de 11 caractères, ...9 autres props }) - 648ns
postMessage({ prop: chaîne de 14 Ko, ...9 autres props }) - 719ns
postMessage({ prop: chaîne de 3 Mo, ...9 autres props }) - 1,26µsNode.js v24.6.0 (pour comparaison) :
postMessage({ prop: chaîne de 11 caractères, ...9 autres props }) - 1,19µs
postMessage({ prop: chaîne de 14 Ko, ...9 autres props }) - 2,69µs
postMessage({ prop: chaîne de 3 Mo, ...9 autres props }) - 304µs// Chemin rapide pour les chaînes - optimisé
postMessage("Hello, worker!");
// Chemin rapide pour les objets simples - optimisé
postMessage({
message: "Hello",
count: 42,
enabled: true,
data: null,
});
// Les objets complexes fonctionnent toujours mais utilisent le clonage structuré standard
postMessage({
nested: { deep: { object: true } },
date: new Date(),
buffer: new ArrayBuffer(8),
});// Sur le thread worker, `postMessage` est automatiquement "routé" vers le thread parent.
postMessage({ hello: "world" });
// Sur le thread principal
worker.postMessage({ hello: "world" });Pour recevoir des messages, utilisez le gestionnaire d'événement message sur le worker et le thread principal.
// Thread worker :
self.addEventListener("message", event => {
console.log(event.data);
});
// ou utilisez le setter :
// self.onmessage = fn
// si sur le thread principal
worker.addEventListener("message", event => {
console.log(event.data);
});
// ou utilisez le setter :
// worker.onmessage = fnTerminaison d'un worker
Une instance Worker se termine automatiquement une fois que sa boucle d'événements n'a plus de travail à faire. Attacher un écouteur "message" sur le global ou tout MessagePort gardera la boucle d'événements active. Pour terminer de force un Worker, appelez worker.terminate().
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
// ...quelque temps plus tard
worker.terminate();Cela provoquera la sortie du worker dès que possible.
process.exit()
Un worker peut se terminer lui-même avec process.exit(). Cela ne termine pas le processus principal. Comme dans Node.js, process.on('beforeExit', callback) et process.on('exit', callback) sont émis sur le thread worker (et non sur le thread principal), et le code de sortie est passé à l'événement "close".
"close"
L'événement "close" est émis lorsqu'un worker a été terminé. Cela peut prendre un certain temps pour que le worker se termine réellement, donc cet événement est émis lorsque le worker a été marqué comme terminé. Le CloseEvent contiendra le code de sortie passé à process.exit(), ou 0 si fermé pour d'autres raisons.
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.addEventListener("close", event => {
console.log("worker is being closed");
});Cet événement n'existe pas dans les navigateurs.
Gestion de la durée de vie
Par défaut, un Worker actif gardera le processus principal (générateur) en vie, donc les tâches asynchrones comme setTimeout et les promesses garderont le processus en vie. Attacher des écouteurs message gardera également le Worker en vie.
worker.unref()
Pour empêcher un worker en cours d'exécution de garder le processus en vie, appelez worker.unref(). Cela découple la durée de vie du worker de la durée de vie du processus principal et est équivalent à ce que fait worker_threads de Node.js.
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();Note : worker.unref() n'est pas disponible dans les navigateurs.
worker.ref()
Pour garder le processus en vie jusqu'à ce que le Worker se termine, appelez worker.ref(). Un worker référencé est le comportement par défaut et a toujours besoin que quelque chose se passe dans la boucle d'événements (comme un écouteur "message") pour que le worker continue de s'exécuter.
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();
// plus tard...
worker.ref();Alternativement, vous pouvez également passer un objet options à Worker :
const worker = new Worker(new URL("worker.ts", import.meta.url).href, {
ref: false,
});Note : worker.ref() n'est pas disponible dans les navigateurs.
Utilisation de la mémoire avec smol
Les instances JavaScript peuvent utiliser beaucoup de mémoire. Le Worker de Bun prend en charge un mode smol qui réduit l'utilisation de la mémoire, au détriment des performances. Pour activer le mode smol, passez smol: true à l'objet options dans le constructeur Worker.
const worker = new Worker("./i-am-smol.ts", {
smol: true,
});Que fait réellement le mode smol ?">
Définir smol: true définit JSC::HeapSize sur Small au lieu de Large par défaut.
Données d'environnement
Partagez des données entre le thread principal et les workers en utilisant setEnvironmentData() et getEnvironmentData().
import { setEnvironmentData, getEnvironmentData } from "worker_threads";
// Dans le thread principal
setEnvironmentData("config", { apiUrl: "https://api.example.com" });
// Dans le worker
const config = getEnvironmentData("config");
console.log(config); // => { apiUrl: "https://api.example.com" }Événements Worker
Écoutez les événements de création de worker en utilisant process.emit() :
process.on("worker", worker => {
console.log("New worker created:", worker.threadId);
});Bun.isMainThread
Vous pouvez vérifier si vous êtes dans le thread principal en vérifiant Bun.isMainThread.
if (Bun.isMainThread) {
console.log("I'm the main thread");
} else {
console.log("I'm in a worker");
}Cela est utile pour exécuter du code de manière conditionnelle selon que vous êtes dans le thread principal ou non.