Блокування event loop
Блокування event loop, це ситуація, коли JavaScript виконує довгу синхронну операцію, і доки вона не завершиться, жодна інша подія не обробляється. Мова однопотокова, тому важкий цикл чи синхронне читання файлу зупиняють і рендеринг, і таймери, і проміси, і мережеві колбеки.
Теорія
TL;DR
- Event loop послідовно виконує задачі: колбеки, проміси, таймери, події.
- JavaScript однопотоковий, у кожен момент часу виконується лише одна інструкція.
- Довга синхронна операція означає, що все інше стоїть у черзі й чекає.
- У браузері це заморожений UI: немає реакції на кліки, скрол і ввід.
- У Node.js це сервер, який не відповідає, бо всі запити стоять за однією CPU-важкою задачею.
- Ліки: chunking, Web Workers, Worker Threads або
child_process, і асинхронні API замість синхронних.
Швидкий приклад
console.log('Start');
for (let i = 0; i < 1e9; i++) {} // blocks the event loop
console.log('End');Поки йде цей цикл:
- браузер не реагує на кліки, скрол, ввід;
- не виконуються таймери й проміси;
- UI «висить».
Що таке event loop
Event loop, це механізм, який:
- послідовно виконує задачі (колбеки, проміси, таймери, події);
- обробляє асинхронні операції;
- відповідає за реакцію інтерфейсу та відгук застосунку.
JavaScript однопотокова мова: у кожен момент часу виконується тільки одна інструкція. Якщо щось виконується довго, все інше стоїть у черзі й чекає.
Чому це погано
1. Зависання інтерфейсу
Користувач не може взаємодіяти із сайтом: кнопки, поля вводу, анімації, усе «заморожено».
2. Асинхронні операції не виконуються
Ані setTimeout, ані fetch, ані Promise.then не запустяться, доки блокування не закінчиться.
setTimeout(() => console.log('1s elapsed'), 1000);
const end = Date.now() + 5000;
while (Date.now() < end) {} // blocks for 5 secondsРезультат:
(after 5 seconds) 1s elapsedТаймер виконався із запізненням: колбек потрапив у чергу вчасно, але черга не оброблялася.
3. Втрата продуктивності в Node.js
Якщо на сервері Node.js виконується CPU-важка задача (наприклад, парсинг великого JSON, шифрування тощо), решта запитів стане в чергу, і сервер перестане відповідати.
Як уникнути блокування
-
Діліть обчислення на частини (chunking):
javascriptfunction heavyTask() { let i = 0; function step() { while (i < 1e9 && i % 1e5 !== 0) i++; if (i < 1e9) setTimeout(step); // hand control back to the event loop } step(); } -
Використовуйте Web Workers (у браузері). Для важких обчислень виконуйте код в окремому потоці, щоб не заважати основному.
-
У Node.js використовуйте Worker Threads або
child_process:javascriptimport { Worker } from 'worker_threads'; new Worker('./heavy-task.js'); -
Не використовуйте синхронні методи вводу-виводу:
fs.readFileSync, погано;fs.readFile, добре;crypto.pbkdf2Sync, погано;crypto.pbkdf2, добре.
Коротко
| Причина | Що відбувається |
|---|---|
| JavaScript однопотоковий | Усе виконується в одному потоці |
| Блокуючий код | Затримує event loop |
| UI перестає реагувати | «Фриз» інтерфейсу |
| Сервер «висне» | Запити не обробляються |
| Рішення | Асинхронність, чанкінг, воркери |
Типові помилки
- Вважати, що
async/awaitроблять код неблокуючим.awaitлише віддає керування на час очікування; сам важкий синхронний обчислювальний блок усерединіasync-функції блокує потік так само. - Синхронні API на сервері.
fs.readFileSync,crypto.pbkdf2Sync,JSON.parseна десятках мегабайтів у обробнику запиту зупиняють увесь процес, а не один запит. - Довіряти точності
setTimeout. Таймер гарантує «не раніше ніж», а не «рівно через»: якщо event loop зайнятий, колбек чекатиме. - Робити chunking через
Promise.resolve().then(). Це мікрозадача, вона виконається в тому самому такті й не дасть браузеру перемалювати кадр. Потрібна макрозадача:setTimeout,MessageChannelабоrequestIdleCallback. - Передавати у Web Worker величезні об'єкти без
Transferable. Структурне клонування великого буфера саме по собі блокує потік; дляArrayBufferвикористовуйте передачу володіння. - Оптимізувати наосліп. Спершу профайлер (вкладка Performance або
--cpu-profу Node.js), і лише потім переписування.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.