Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке web workers?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Web Workers** запускають JavaScript у фоновому потоці (worker), ізольованому від головного, щоб важкі обчислення не заморожували UI. ```javascript const worker = new Worker('worker.js'); worker.postMessage(21); worker.onmessage = (e) => console.log(e.data); // 42 // worker.js: self.onmessage = (e) => self.postMessage(e.data * 2); ``` **Головне:** Worker не має доступу до DOM; дані передаються через `postMessage` з глибоким копіюванням, а не через спільну пам'ять.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Web Workers** дозволяють запускати JavaScript у фонових потоках, ізольованих від головного, щоб важкі обчислення не заморожували UI. ## Теорія ### TL;DR - Головний потік як кухар, що готує страви по одній. Web Workers - це помічники у підсобці, які передають готовий результат через записки - Немає доступу до DOM, немає спільної пам'яті, тільки передача повідомлень через `postMessage` / `onmessage` - Дані копіюються глибоко через алгоритм structured clone - не передаються за посиланням - Правило: якщо обчислення блокує UI більше ніж на 50мс, виноси у Worker - Не варто: для DOM-оновлень, швидких задач до 10мс або даних які не клонуються ### Швидкий приклад ```javascript // main.js const worker = new Worker('worker.js'); worker.postMessage(21); // надсилаємо вхідне значення worker.onmessage = (e) => console.log(e.data); // виводить: 42 // worker.js self.onmessage = (e) => { const result = e.data * 2; // важкі обчислення не заморозять UI self.postMessage(result); // відповідь до головного потоку }; ``` Worker запускає `worker.js` в окремому потоці. `postMessage` надсилає дані, `onmessage` їх отримує з іншого боку. Головний потік весь цей час залишається вільним. ### Модель ізоляції Web Worker запускає повністю ізольований JavaScript-контекст зі своїм глобальним об'єктом (`self`), без доступу до `window`, `document` або DOM. Коли викликаєш `postMessage`, браузер використовує алгоритм structured clone, щоб глибоко скопіювати дані перед передачею між потоками. Обидва потоки завжди отримують власну копію. Це усуває помилки зі спільним станом, які роблять багатопотоковий код в інших мовах таким складним. Головне тут одне: між головним потоком і Worker за замовчуванням немає спільної пам'яті. Ти передаєш дані, а не посилання. ### Коли використовувати Використовуй Web Worker коли: - Цикл або обчислення займає більше 50мс і сторінка підгальмовує - Обробляєш пікселі canvas для великих зображень (4K і вище) - Парсиш JSON або CSV файли розміром більше 1МБ - Потрібні криптографічні операції або ML-інференс у браузері Не варто коли: - Задача потребує читання або запису DOM - Операція завершується менш ніж за 10мс - Дані, які треба передати, не клонуються (функції, DOM-вузли, об'єкти з методами прототипу) ### Як браузер створює воркери Chrome (V8) і Firefox (SpiderMonkey) створюють реальний потік ОС через примітиви на зразок pthreads. Рушій JS парсить і компілює скрипт воркера в цьому потоці повністю ізольовано. Повідомлення накопичуються в окремому каналі на кожен Worker. Крок structured clone блокує відправника на час серіалізації, тому відправка 100МБ ArrayBuffer синхронно - дорога операція. Для великих бінарних даних є `SharedArrayBuffer` (вимагає заголовків `COOP`/`COEP`), або можна передати право власності через `postMessage(buffer, [buffer])` без копіювання. ### Типові помилки **Спроба звернутися до DOM:** ```javascript // worker.js - одразу викине помилку document.getElementById('log').innerText = 'done'; // ReferenceError: document is not defined ``` У Worker немає `document`. Надішли повідомлення до головного потоку і нехай він оновить DOM. **Передача даних, що не клонуються:** ```javascript // main.js - викине помилку worker.postMessage({ fn: () => console.log('hi') }); // DataCloneError ``` Функції, DOM-вузли та об'єкти з методами прототипу не клонуються. Передавай прості значення, об'єкти, масиви, Blob або ArrayBuffer. **Припущення про спільні змінні:** ```javascript let count = 0; // main.js worker.postMessage(count++); // worker бачить 0, main збільшує до 1 - це окремі контексти ``` Ізольовані контексти означають відсутність спільних глобальних змінних. Завжди передавай весь стан який потрібен через повідомлення. **Забуваєш викликати terminate:** Worker займає пам'ять поки не викликати `terminate()`. Це та помилка яку я бачу найчастіше на code review: React-компонент створює Worker всередині `useEffect`, але не повертає нічого з того ефекту, і Worker живе вічно. Виклик `worker.terminate()` в cleanup - обов'язковий. Пул з 4 воркерів є стандартною практикою; створення нового на кожну задачу додає 50-200мс накладних витрат. **Великі повідомлення:** Клонування 100МБ ArrayBuffer блокує обидва потоки на 500мс і більше. Розбивай дані на частини або використовуй transferable objects: `worker.postMessage(buffer, [buffer])` передає право власності замість копіювання. ### Де зустрічається - Figma запускає розрахунки макету для режиму прототипування у Workers, щоб canvas тримав 60fps - Fabric.js використовує Workers для парсингу SVG в редакторах canvas - Three.js виносить компіляцію WebGL шейдерів з головного потоку, щоб уникнути просадок fps - Photopea обробляє фільтри 4K зображень у Workers через OffscreenCanvas - Comlink (використовується у Vercel) обгортає Workers у proxy-об'єкти, щоб вони виглядали як звичайні async-функції ### Питання на співбесіді **Q:** Як передавати дані між головним потоком і Worker? **A:** `postMessage` надсилає дані, `onmessage` отримує їх з іншого боку. Дані глибоко копіюються через алгоритм structured clone, тому зміни на одному боці не впливають на інший. **Q:** Які типи даних можна передати через `postMessage`? **A:** Прості значення, звичайні об'єкти, масиви, Blob, ArrayBuffer та ImageData. Функції, DOM-вузли та об'єкти з Symbol-ключами викидають `DataCloneError`. **Q:** Яка різниця між Worker і SharedWorker? **A:** `SharedWorker` дозволяє кільком вкладкам або скриптам підключатися до одного і того ж екземпляра воркера за ім'ям, ділячи один фоновий потік. Звичайний Worker належить одному скрипту. **Q:** Як дебажити Web Workers? **A:** У Chrome DevTools відкрий Sources і знайди іконку Worker у лівій бічній панелі. Кожен Worker має власну область з точками зупинки та окремою консоллю. **Q:** (Senior) Чому реалізовують пул воркерів замість створення нового на кожну задачу? **A:** Створення Worker займає 50-200мс. Пул з 4-8 воркерів амортизує цю вартість, повторно використовуючи потоки. Задачі стають у чергу і розподіляються до наступного вільного Worker через round-robin. Неактивні Workers завершуються через 30 секунд, щоб звільнити пам'ять. ## Приклади ### Базовий: подвоїти число у фоні ```javascript // worker.js self.onmessage = (e) => { const result = e.data * 2; self.postMessage(result); }; // main.js const worker = new Worker('worker.js'); worker.postMessage(21); worker.onmessage = (e) => console.log(e.data); // 42 worker.terminate(); // звільняємо ресурси ``` Найпростіший можливий Worker: отримати число, повернути число. Головний потік не блокується поки відбувається обчислення. ### Проміжний: фільтр grayscale з OffscreenCanvas ```javascript // ImageFilterWorker.js self.onmessage = async (e) => { const { imageData } = e.data; const canvas = new OffscreenCanvas(imageData.width, imageData.height); const ctx = canvas.getContext('2d'); ctx.putImageData(imageData, 0, 0); const out = ctx.getImageData(0, 0, canvas.width, canvas.height); for (let i = 0; i < out.data.length; i += 4) { // формула яскравості const gray = 0.3 * out.data[i] + 0.59 * out.data[i + 1] + 0.11 * out.data[i + 2]; out.data[i] = out.data[i + 1] = out.data[i + 2] = gray; } ctx.putImageData(out, 0, 0); self.postMessage(await canvas.convertToBlob()); }; // React-компонент const worker = new Worker('ImageFilterWorker.js'); worker.onmessage = (e) => setFilteredImage(URL.createObjectURL(e.data)); worker.postMessage({ imageData: ctx.getImageData(0, 0, width, height) }); ``` Обробка пікселів 4K зображення в головному потоці заморозила б UI на кілька секунд. У Worker компонент залишається інтерактивним весь цей час. `OffscreenCanvas` дає Worker повний canvas API без будь-якої залежності від DOM. ### Просунутий: обробка помилок і завершення ```javascript // main.js const worker = new Worker('error-worker.js'); worker.onerror = (e) => { console.error('Worker впав:', e.message); // "Intentional crash" worker.terminate(); }; worker.postMessage('crash'); // error-worker.js self.onmessage = (e) => { if (e.data === 'crash') throw new Error('Intentional crash'); self.postMessage('ok'); }; ``` Необроблені помилки у Worker запускають `onerror` в головному потоці. Сам головний потік при цьому не падає. Але якщо `onerror` не приєднано, помилка просто зникає. Завжди вішай його. `terminate()` вбиває Worker миттєво без жодного callback - зберігай потрібний стан до виклику.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.