Skip to main content

Блокування event loop

Блокування event loop, це ситуація, коли JavaScript виконує довгу синхронну операцію, і доки вона не завершиться, жодна інша подія не обробляється. Мова однопотокова, тому важкий цикл чи синхронне читання файлу зупиняють і рендеринг, і таймери, і проміси, і мережеві колбеки.

Теорія

TL;DR

  • Event loop послідовно виконує задачі: колбеки, проміси, таймери, події.
  • JavaScript однопотоковий, у кожен момент часу виконується лише одна інструкція.
  • Довга синхронна операція означає, що все інше стоїть у черзі й чекає.
  • У браузері це заморожений UI: немає реакції на кліки, скрол і ввід.
  • У Node.js це сервер, який не відповідає, бо всі запити стоять за однією CPU-важкою задачею.
  • Ліки: chunking, Web Workers, Worker Threads або child_process, і асинхронні API замість синхронних.

Швидкий приклад

javascript
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 не запустяться, доки блокування не закінчиться.

javascript
setTimeout(() => console.log('1s elapsed'), 1000); const end = Date.now() + 5000; while (Date.now() < end) {} // blocks for 5 seconds

Результат:

text
(after 5 seconds) 1s elapsed

Таймер виконався із запізненням: колбек потрапив у чергу вчасно, але черга не оброблялася.

3. Втрата продуктивності в Node.js

Якщо на сервері Node.js виконується CPU-важка задача (наприклад, парсинг великого JSON, шифрування тощо), решта запитів стане в чергу, і сервер перестане відповідати.

Як уникнути блокування

  1. Діліть обчислення на частини (chunking):

    javascript
    function 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(); }
  2. Використовуйте Web Workers (у браузері). Для важких обчислень виконуйте код в окремому потоці, щоб не заважати основному.

  3. У Node.js використовуйте Worker Threads або child_process:

    javascript
    import { Worker } from 'worker_threads'; new Worker('./heavy-task.js');
  4. Не використовуйте синхронні методи вводу-виводу:

    • 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), і лише потім переписування.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.