Что такое Web Workers в JavaScript?
Web Workers в JavaScript
JavaScript в браузере является однопоточным: весь код, работа с DOM, обработка событий и рендеринг выполняются в одном главном потоке. При длительных вычислениях главный поток блокируется, интерфейс перестаёт реагировать на действия пользователя.
Web Workers решают эту проблему, предоставляя возможность запускать скрипты в отдельных фоновых потоках операционной системы.
Типы Web Workers
- Dedicated Worker — создаётся одной страницей и доступен только ей. Самый распространённый тип.
- Shared Worker — может быть разделён между несколькими вкладками, фреймами и воркерами одного источника.
- Service Worker — особый тип, работающий как прокси между браузером и сетью; используется для офлайн-кэширования и push-уведомлений.
Ключевые ограничения
Web Workers не имеют доступа к:
- объекту
windowи DOM document,parent- некоторым Web API (например,
localStorageнапрямую)
Воркеры имеют доступ к navigator, location, XMLHttpRequest, fetch, IndexedDB, setTimeout/setInterval, WebSocket, importScripts().
Модель обмена сообщениями
Связь между главным потоком и воркером осуществляется через postMessage / onmessage. Данные передаются по значению через структурированное клонирование (structured clone algorithm), а не по ссылке.
Для передачи больших объёмов данных (например, ArrayBuffer) без копирования используется механизм Transferable Objects — объект передаётся с передачей владения, и исходный контекст теряет к нему доступ.
Жизненный цикл воркера
- Главный поток создаёт воркер через
new Worker(url) - Браузер загружает скрипт воркера в отдельном потоке
- Обмен сообщениями через
postMessage/onmessage - Воркер завершается через
worker.terminate()(из главного потока) илиself.close()(изнутри воркера)
Практические сценарии применения
- Парсинг и обработка больших JSON-файлов
- Криптографические операции
- Сжатие/распаковка данных
- Обработка изображений (фильтры, трансформации пикселей)
- Сложные математические вычисления (физические симуляции, ML-инференс)
- Обработка данных в реальном времени (audio, video)
Inline Workers
Воркер можно создать без отдельного файла, используя Blob и URL.createObjectURL, что удобно при бандлинге.
const code = `self.onmessage = (e) => self.postMessage(e.data * 2);`;
const blob = new Blob([code], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
Производительность и подводные камни
- Создание воркера имеет overhead — не стоит создавать их на каждый запрос; используйте пул воркеров.
- Структурированное клонирование больших объектов может быть дороже, чем само вычисление.
- Ошибки в воркере не всплывают в главный поток автоматически — нужно обрабатывать событие
onerror. - В Node.js аналогом являются
worker_threadsиз модуляnode:worker_threads.
Что хочет услышать интервьюер
Понимание однопоточной природы JavaScript и проблемы блокировки главного потока
Знание типов воркеров (Dedicated, Shared, Service) и их различий
Понимание модели обмена сообщениями: postMessage, structured clone, Transferable Objects
Осознание ограничений воркеров (нет доступа к DOM, window) и какие API доступны
Практический опыт: реальные сценарии применения, пулы воркеров, обработка ошибок
Пример: Dedicated Worker: тяжёлые вычисления без блокировки UI
// worker.ts
self.onmessage = (event: MessageEvent<number>) => {
const n = event.data;
// Тяжёлое вычисление (например, факториал через BigInt)
let result = 1n;
for (let i = 2n; i <= BigInt(n); i++) {
result *= i;
}
self.postMessage(result.toString());
};
// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url), {
type: 'module',
});
worker.postMessage(50000); // не блокирует UI
worker.onmessage = (event: MessageEvent<string>) => {
console.log('Результат:', event.data);
};
worker.onerror = (error) => {
console.error('Ошибка в воркере:', error.message);
};
Пример: Transferable Objects: передача ArrayBuffer без копирования
// main.ts — передаём большой буфер с передачей владения
const buffer = new ArrayBuffer(1024 * 1024 * 64); // 64 МБ
const worker = new Worker('./image-worker.js');
// Третий аргумент — список transferable: буфер не копируется
worker.postMessage({ buffer }, [buffer]);
// После передачи buffer.byteLength === 0 — владение передано воркеру
console.log(buffer.byteLength); // 0
// image-worker.js
self.onmessage = (event) => {
const { buffer } = event.data;
const view = new Uint8Array(buffer);
// Обрабатываем пиксели изображения
for (let i = 0; i < view.length; i++) {
view[i] = 255 - view[i]; // инверсия
}
// Возвращаем обратно с передачей владения
self.postMessage({ buffer }, [buffer]);
};
Пример: Пул воркеров для параллельной обработки задач
class WorkerPool {
private workers: Worker[];
private queue: Array<{ resolve: Function; reject: Function; data: unknown }> = [];
private idle: Worker[];
constructor(private readonly size: number, scriptUrl: string) {
this.workers = Array.from({ length: size }, () => new Worker(scriptUrl));
this.idle = [...this.workers];
this.workers.forEach((worker) => {
worker.onmessage = (e) => {
// Воркер освободился — берём следующую задачу
this.idle.push(worker);
this.processQueue();
// Найти резолвер для этого воркера (упрощённо)
};
});
}
run(data: unknown): Promise<unknown> {
return new Promise((resolve, reject) => {
this.queue.push({ resolve, reject, data });
this.processQueue();
});
}
private processQueue() {
if (this.queue.length === 0 || this.idle.length === 0) return;
const worker = this.idle.pop()!;
const task = this.queue.shift()!;
worker.onmessage = (e) => {
task.resolve(e.data);
this.idle.push(worker);
this.processQueue();
};
worker.onerror = (e) => task.reject(e);
worker.postMessage(task.data);
}
terminate() {
this.workers.forEach((w) => w.terminate());
}
}
// Использование
const pool = new WorkerPool(4, './compute-worker.js');
const results = await Promise.all(
[1000, 2000, 3000, 4000].map((n) => pool.run(n))
);
pool.terminate();
Типичные ошибки
Путают Web Workers с асинхронностью (Promise, async/await) — воркеры дают настоящий параллелизм, а не конкурентность в одном потоке
Не учитывают стоимость сериализации при передаче данных и не используют Transferable Objects для больших буферов
Создают новый воркер на каждый запрос вместо переиспользования или пула воркеров
Забывают обрабатывать событие onerror — ошибки в воркере незаметно теряются
Пытаются обращаться к DOM или localStorage из воркера, не зная о его ограничениях


