JavaScript однопоточен — весь твой код выполняется в одном потоке. Если что-то тяжёлое занимает этот поток надолго, интерфейс «замерзает»: перестаёт реагировать на клики, не обновляется анимация. Web Workers решают эту проблему, позволяя запускать JS-код в отдельном потоке.
Главный поток (main thread) — один поток, где работает весь UI: DOM, события, CSS-анимации, React/Vue. Если ты запустишь здесь тяжёлые вычисления, страница зависнет.
Worker thread — отдельный поток без доступа к DOM. Идеален для долгих вычислений: обработки данных, шифрования, парсинга большого JSON, обработки изображений.
// main.js — главный поток
const worker = new Worker('worker.js')
// Отправляем данные в worker
worker.postMessage({ type: 'compute', data: [1, 2, 3] })
// Получаем результат
worker.onmessage = (event) => {
console.log('Результат:', event.data)
}
// worker.js — код в отдельном потоке
self.onmessage = (event) => {
const result = heavyComputation(event.data)
self.postMessage(result)
}Главный поток и worker общаются только через сообщения. Данные копируются при передаче — изменение данных в worker не влияет на оригинал. Это предотвращает проблемы с параллельным доступом.
Можно передавать: примитивы, объекты, массивы, ArrayBuffer. Нельзя: функции, DOM-элементы, замыкания.
Для больших данных (например, изображений) копирование медленно. Вместо этого можно передать владение объектом через второй аргумент postMessage — данные перемещаются, а не копируются, и оригинал становится пустым:
const buffer = new ArrayBuffer(1024 * 1024) // 1MB
worker.postMessage(buffer, [buffer]) // передаём владение
// buffer теперь пуст в главном потокеSharedArrayBuffer — буфер, который разделяется между потоками без копирования. Оба потока читают и пишут в один и тот же кусок памяти. Требует специальных заголовков безопасности (COOP/COEP) из-за Spectre-уязвимости. Для синхронизации используется Atomics API.
Workers не имеют доступа к:
document, window, DOMlocalStorage (но есть доступ к IndexedDB)Зато доступны: fetch, WebSocket, IndexedDB, crypto, setTimeout, console.
Симуляция Worker через EventEmitter: вычисление числа Фибоначчи в «отдельном потоке»
// Симулируем Worker API через EventEmitter в Node.js
// В браузере это работало бы через настоящие Web Workers
class EventEmitter {
constructor() { this._listeners = {} }
on(event, fn) {
(this._listeners[event] = this._listeners[event] || []).push(fn)
return this
}
emit(event, data) {
(this._listeners[event] || []).forEach(fn => fn(data))
}
}
// --- Код «worker-потока» ---
function workerThread(port) {
port.on('message', (event) => {
const { id, type, data } = event
if (type === 'fibonacci') {
// Тяжёлое вычисление — было бы не в главном потоке
function fib(n) {
if (n <= 1) return n
return fib(n - 1) + fib(n - 2)
}
const result = fib(data)
port.emit('message', { id, result })
}
if (type === 'primes') {
// Найти все простые числа до n
const sieve = new Array(data + 1).fill(true)
sieve[0] = sieve[1] = false
for (let i = 2; i * i <= data; i++) {
if (sieve[i]) {
for (let j = i * i; j <= data; j += i) sieve[j] = false
}
}
const result = sieve.reduce((acc, v, i) => v ? [...acc, i] : acc, [])
port.emit('message', { id, result })
}
})
}
// --- Код «главного потока» ---
class SimulatedWorker {
constructor() {
this._mainPort = new EventEmitter()
this._workerPort = new EventEmitter()
this._pendingTasks = new Map()
this._taskIdCounter = 0
// Связываем порты — сообщение одному идёт к другому
this._mainPort.on('message', (data) => this._workerPort.emit('message', data))
this._workerPort.on('message', (data) => this._mainPort.emit('message', data))
// Запускаем «worker»
workerThread(this._workerPort)
// Обрабатываем ответы worker
this._mainPort.on('message', ({ id, result }) => {
const resolve = this._pendingTasks.get(id)
if (resolve) {
resolve(result)
this._pendingTasks.delete(id)
}
})
}
postMessage(type, data) {
return new Promise((resolve) => {
const id = ++this._taskIdCounter
this._pendingTasks.set(id, resolve)
this._mainPort.emit('message', { id, type, data })
})
}
}
// Использование
const worker = new SimulatedWorker()
console.log('Главный поток: запускаем тяжёлые вычисления...')
const fib35 = await worker.postMessage('fibonacci', 35)
console.log(`Fibonacci(35) = ${fib35}`) // 9227465
const primes = await worker.postMessage('primes', 50)
console.log(`Простые до 50: ${primes.join(', ')}`)
// 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47
console.log('Главный поток: UI не зависал во время вычислений')JavaScript однопоточен — весь твой код выполняется в одном потоке. Если что-то тяжёлое занимает этот поток надолго, интерфейс «замерзает»: перестаёт реагировать на клики, не обновляется анимация. Web Workers решают эту проблему, позволяя запускать JS-код в отдельном потоке.
Главный поток (main thread) — один поток, где работает весь UI: DOM, события, CSS-анимации, React/Vue. Если ты запустишь здесь тяжёлые вычисления, страница зависнет.
Worker thread — отдельный поток без доступа к DOM. Идеален для долгих вычислений: обработки данных, шифрования, парсинга большого JSON, обработки изображений.
// main.js — главный поток
const worker = new Worker('worker.js')
// Отправляем данные в worker
worker.postMessage({ type: 'compute', data: [1, 2, 3] })
// Получаем результат
worker.onmessage = (event) => {
console.log('Результат:', event.data)
}
// worker.js — код в отдельном потоке
self.onmessage = (event) => {
const result = heavyComputation(event.data)
self.postMessage(result)
}Главный поток и worker общаются только через сообщения. Данные копируются при передаче — изменение данных в worker не влияет на оригинал. Это предотвращает проблемы с параллельным доступом.
Можно передавать: примитивы, объекты, массивы, ArrayBuffer. Нельзя: функции, DOM-элементы, замыкания.
Для больших данных (например, изображений) копирование медленно. Вместо этого можно передать владение объектом через второй аргумент postMessage — данные перемещаются, а не копируются, и оригинал становится пустым:
const buffer = new ArrayBuffer(1024 * 1024) // 1MB
worker.postMessage(buffer, [buffer]) // передаём владение
// buffer теперь пуст в главном потокеSharedArrayBuffer — буфер, который разделяется между потоками без копирования. Оба потока читают и пишут в один и тот же кусок памяти. Требует специальных заголовков безопасности (COOP/COEP) из-за Spectre-уязвимости. Для синхронизации используется Atomics API.
Workers не имеют доступа к:
document, window, DOMlocalStorage (но есть доступ к IndexedDB)Зато доступны: fetch, WebSocket, IndexedDB, crypto, setTimeout, console.
Симуляция Worker через EventEmitter: вычисление числа Фибоначчи в «отдельном потоке»
// Симулируем Worker API через EventEmitter в Node.js
// В браузере это работало бы через настоящие Web Workers
class EventEmitter {
constructor() { this._listeners = {} }
on(event, fn) {
(this._listeners[event] = this._listeners[event] || []).push(fn)
return this
}
emit(event, data) {
(this._listeners[event] || []).forEach(fn => fn(data))
}
}
// --- Код «worker-потока» ---
function workerThread(port) {
port.on('message', (event) => {
const { id, type, data } = event
if (type === 'fibonacci') {
// Тяжёлое вычисление — было бы не в главном потоке
function fib(n) {
if (n <= 1) return n
return fib(n - 1) + fib(n - 2)
}
const result = fib(data)
port.emit('message', { id, result })
}
if (type === 'primes') {
// Найти все простые числа до n
const sieve = new Array(data + 1).fill(true)
sieve[0] = sieve[1] = false
for (let i = 2; i * i <= data; i++) {
if (sieve[i]) {
for (let j = i * i; j <= data; j += i) sieve[j] = false
}
}
const result = sieve.reduce((acc, v, i) => v ? [...acc, i] : acc, [])
port.emit('message', { id, result })
}
})
}
// --- Код «главного потока» ---
class SimulatedWorker {
constructor() {
this._mainPort = new EventEmitter()
this._workerPort = new EventEmitter()
this._pendingTasks = new Map()
this._taskIdCounter = 0
// Связываем порты — сообщение одному идёт к другому
this._mainPort.on('message', (data) => this._workerPort.emit('message', data))
this._workerPort.on('message', (data) => this._mainPort.emit('message', data))
// Запускаем «worker»
workerThread(this._workerPort)
// Обрабатываем ответы worker
this._mainPort.on('message', ({ id, result }) => {
const resolve = this._pendingTasks.get(id)
if (resolve) {
resolve(result)
this._pendingTasks.delete(id)
}
})
}
postMessage(type, data) {
return new Promise((resolve) => {
const id = ++this._taskIdCounter
this._pendingTasks.set(id, resolve)
this._mainPort.emit('message', { id, type, data })
})
}
}
// Использование
const worker = new SimulatedWorker()
console.log('Главный поток: запускаем тяжёлые вычисления...')
const fib35 = await worker.postMessage('fibonacci', 35)
console.log(`Fibonacci(35) = ${fib35}`) // 9227465
const primes = await worker.postMessage('primes', 50)
console.log(`Простые до 50: ${primes.join(', ')}`)
// 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47
console.log('Главный поток: UI не зависал во время вычислений')Реализуй createWorkerPool(workerCount) — пул из нескольких «воркеров», которые параллельно обрабатывают задачи. Метод submit(taskType, data) возвращает Promise с результатом. Метод getStats() возвращает объект {active, queued, completed}. Воркеры симулируются через setTimeout. Каждый воркер может выполнять только одну задачу одновременно.
workers.find(w => !w.busy) найдёт свободного воркера. Для reverse задачи используй data.split("").reverse().join(""). Случайная задержка: Math.floor(Math.random() * 100) + 50. Не забудь увеличить счётчики stats при добавлении в очередь и уменьшить при завершении.