Skip to content

Worker позволяет запускать и взаимодействовать с новым экземпляром JavaScript, работающим в отдельном потоке, при этом разделяя ресурсы ввода-вывода с основным потоком.

Bun реализует минимальную версию Web Workers API с расширениями, которые делают его более подходящим для серверных случаев использования. Как и остальная часть Bun, Worker в Bun поддерживает CommonJS, ES Modules, TypeScript, JSX, TSX и многое другое из коробки. Никаких дополнительных шагов сборки не требуется.

Создание Worker

Как в браузерах, Worker является глобальным. Используйте его для создания нового потока worker.

Из основного потока

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

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

Поток worker

ts
// предотвращает ошибки TS
declare var self: Worker;

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

Чтобы предотвратить ошибки TypeScript при использовании self, добавьте эту строку в начало вашего файла worker.

ts
declare var self: Worker;

Вы можете использовать синтаксис import и export в вашем коде worker. В отличие от браузеров, нет необходимости указывать {type: "module"} для использования ES Modules.

Для упрощения обработки ошибок начальный скрипт для загрузки разрешается во время вызова new Worker(url).

js
const worker = new Worker("/not-found.js");
// выбрасывает ошибку немедленно

Спецификатор, переданный в Worker, разрешается относительно корня проекта (как ввод bun ./path/to/file.js).

preload - загрузка модулей перед запуском worker

Вы можете передать массив спецификаторов модулей в опцию preload для загрузки модулей перед запуском worker. Это полезно, когда вы хотите убедиться, что некоторый код всегда загружается перед запуском приложения, например, загрузка OpenTelemetry, Sentry, DataDog и т.д.

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

Как и аргумент CLI --preload, опция preload обрабатывается перед запуском worker.

Вы также можете передать одну строку в опцию preload:

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

URL blob:

Вы также можете передать URL blob: в Worker. Это полезно для создания workers из строк или других источников.

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

Как и остальная часть Bun, workers, созданные из URL blob:, поддерживают TypeScript, JSX и другие типы файлов из коробки. Вы можете указать, что он должен быть загружен через typescript, либо через type, либо передав filename в конструктор 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"

Событие "open" испускается, когда worker создан и готов к получению сообщений. Это можно использовать для отправки начального сообщения worker, когда он готов. (Это событие не существует в браузерах.)

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

worker.addEventListener("open", () => {
  console.log("worker готов");
});

Сообщения автоматически ставятся в очередь, пока worker не будет готов, поэтому нет необходимости ждать события "open" для отправки сообщений.

Сообщения с postMessage

Для отправки сообщений используйте worker.postMessage и self.postMessage. Это использует алгоритм структурированного клонирования HTML.

Оптимизации производительности

Bun включает оптимизированные быстрые пути для postMessage, чтобы значительно улучшить производительность для распространенных типов данных:

Быстрый путь для строк - При отправке чистых строковых значений Bun полностью обходит алгоритм структурированного клонирования, достигая значительного прироста производительности без накладных расходов на сериализацию.

Быстрый путь для простых объектов - Для обычных объектов, содержащих только примитивные значения (строки, числа, булевы значения, null, undefined), Bun использует оптимизированный путь сериализации, который хранит свойства напрямую без полного структурированного клонирования.

Быстрый путь для простых объектов активируется, когда объект:

  • Является простым объектом без модификаций цепочки прототипов
  • Содержит только перечисляемые, настраиваемые свойства данных
  • Не имеет индексированных свойств или методов getter/setter
  • Все значения свойств являются примитивами или строками

С этими быстрыми путями postMessage от Bun работает в 2-241 раза быстрее, потому что длина сообщения больше не оказывает значительного влияния на производительность.

Bun (с быстрыми путями):

ts
postMessage({ prop: строка 11 символов, ...еще 9 свойств }) - 648нс
postMessage({ prop: строка 14 КБ, ...еще 9 свойств })    - 719нс
postMessage({ prop: строка 3 МБ, ...еще 9 свойств })     - 1.26мкс

Node.js v24.6.0 (для сравнения):

js
postMessage({ prop: строка 11 символов, ...еще 9 свойств }) - 1.19мкс
postMessage({ prop: строка 14 КБ, ...еще 9 свойств })    - 2.69мкс
postMessage({ prop: строка 3 МБ, ...еще 9 свойств })     - 304мкс
js
// Быстрый путь для строк - оптимизировано
postMessage("Hello, worker!");

// Быстрый путь для простых объектов - оптимизировано
postMessage({
  message: "Hello",
  count: 42,
  enabled: true,
  data: null,
});

// Сложные объекты все еще работают, но используют стандартное структурированное клонирование
postMessage({
  nested: { deep: { object: true } },
  date: new Date(),
  buffer: new ArrayBuffer(8),
});
js
// В потоке worker, `postMessage` автоматически "маршрутизируется" в родительский поток.
postMessage({ hello: "world" });

// В основном потоке
worker.postMessage({ hello: "world" });

Для получения сообщений используйте обработчик событий message в worker и основном потоке.

js
// Поток worker:
self.addEventListener("message", event => {
  console.log(event.data);
});
// или используйте setter:
// self.onmessage = fn

// если в основном потоке
worker.addEventListener("message", event => {
  console.log(event.data);
});
// или используйте setter:
// worker.onmessage = fn

Завершение worker

Экземпляр Worker завершается автоматически, как только в его цикле событий не остается работы. Привязка слушателя "message" на глобальном объекте или любом MessagePort будет поддерживать цикл событий активным. Для принудительного завершения Worker вызовите worker.terminate().

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

// ...некоторое время спустя
worker.terminate();

Это заставит worker выйти как можно скорее.

process.exit()

Worker может завершить себя с помощью process.exit(). Это не завершает основной процесс. Как в Node.js, process.on('beforeExit', callback) и process.on('exit', callback) испускаются в потоке worker (а не в основном потоке), и код выхода передается событию "close".

"close"

Событие "close" испускается, когда worker был завершен. Может потребоваться некоторое время для фактического завершения worker, поэтому это событие испускается, когда worker был помечен как завершенный. CloseEvent будет содержать код выхода, переданный в process.exit(), или 0, если закрыт по другим причинам.

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

worker.addEventListener("close", event => {
  console.log("worker закрывается");
});

Это событие не существует в браузерах.

Управление временем жизни

По умолчанию активный Worker будет поддерживать основной (породивший) процесс живым, поэтому асинхронные задачи, такие как setTimeout и промисы, будут поддерживать процесс живым. Привязка слушателей message также будет поддерживать Worker живым.

worker.unref()

Чтобы остановить работающий worker от поддержания процесса живым, вызовите worker.unref(). Это развязывает время жизни worker от времени жизни основного процесса и эквивалентно тому, что делает worker_threads в Node.js.

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

Примечание: worker.unref() недоступен в браузерах.

worker.ref()

Чтобы поддерживать процесс живым до завершения Worker, вызовите worker.ref(). ref'd worker является поведением по умолчанию, и все еще требует чего-то происходящего в цикле событий (например, слушателя "message") для продолжения работы worker.

ts
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();
// позже...
worker.ref();

Или вы также можете передать объект options в Worker:

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

Примечание: worker.ref() недоступен в браузерах.

Использование памяти с smol

Экземпляры JavaScript могут использовать много памяти. Worker от Bun поддерживает режим smol, который уменьшает использование памяти за счет производительности. Чтобы включить режим smol, передайте smol: true в объект options в конструкторе Worker.

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

Что на самом деле делает режим smol?">

Установка smol: true устанавливает JSC::HeapSize в Small вместо значения по умолчанию Large.

Данные окружения

Обменивайтесь данными между основным потоком и workers с помощью setEnvironmentData() и getEnvironmentData().

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

// В основном потоке
setEnvironmentData("config", { apiUrl: "https://api.example.com" });

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

События Worker

Слушайте события создания worker с помощью process.emit():

ts
process.on("worker", worker => {
  console.log("Создан новый worker:", worker.threadId);
});

Bun.isMainThread

Вы можете проверить, находитесь ли вы в основном потоке, проверив Bun.isMainThread.

ts
if (Bun.isMainThread) {
  console.log("Я в основном потоке");
} else {
  console.log("Я в worker");
}

Это полезно для условного запуска кода в зависимости от того, находитесь ли вы в основном потоке или нет.

Bun от www.bunjs.com.cn