Skip to main content

Нескінченний цикл

Якщо у функції є нескінченний цикл, JavaScript ніколи не звільнить стек викликів, і event loop зупиниться: жодне інше завдання, ані таймери, ані проміси, ані кліки, ані відмальовування інтерфейсу, не зможе виконатися. Для користувача це виглядає як повністю «зависла» сторінка.

Теорія

TL;DR

  • Нескінченний цикл не дає функції завершитися, тому вона назавжди залишається в стеку викликів (call stack).
  • Поки стек зайнятий, event loop не може взяти нову задачу з черги.
  • Не спрацюють ані setTimeout, ані Promise.then, ані обробники подій.
  • Браузер перестає перемальовувати UI і згодом пропонує закрити вкладку.
  • Рішення: розбивати роботу на частини (setTimeout, requestAnimationFrame) або виносити обчислення у Web Worker.

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

javascript
function infinite() { while (true) { console.log('forever...'); } } infinite();

Що відбувається крок за кроком

  1. Функція infinite() потрапляє в call stack.
  2. Починається виконання циклу while (true), і він ніколи не завершиться.
  3. Рушій ніколи не вийде з функції, а отже:
    • стек ніколи не звільниться,
    • event loop не зможе взяти наступну задачу,
    • браузер перестане реагувати на кліки, скрол, а іноді «зависають» навіть DevTools.

Чому це відбувається

JavaScript це однопотокова мова. У кожен момент часу вона може виконувати лише один шматок коду, той, що зараз перебуває у call stack.

Поки функція не завершилась, рушій не перемкнеться на інші задачі: ані на setTimeout, ані на Promise.then. Черга завдань просто накопичується і чекає на вільний стек, якого вже не буде.

Таймер, який ніколи не спрацює

javascript
setTimeout(() => console.log('I will not run'), 0); while (true) {} // infinite loop

Пояснення:

  • setTimeout кладе колбек у чергу макрозадач;
  • поки працює while (true), стек не звільняється;
  • event loop не може дістати задачу з черги;
  • console.log('I will not run') ніколи не виконається.

Наслідки

НаслідокОпис
Блокування стекаКод не виходить з функції, тому стек не звільняється
Зупинка event loopЧерги задач (таймери, проміси) не обробляються
Зависання сторінкиUI не перемальовується, браузер може показати «Сторінка не відповідає»
Немає реакції на подіїКліки, скрол, введення тексту не працюють

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

Використовуйте асинхронність або розбиття задачі на частини.

Варіант 1: setTimeout

javascript
function loop() { console.log('iteration'); setTimeout(loop, 0); // lets the engine breathe between iterations } loop();

Варіант 2: requestAnimationFrame, підходить для візуальних задач і анімацій:

javascript
function animate() { // draw a frame requestAnimationFrame(animate); } animate();

Варіант 3: Web Worker, якщо потрібні важкі обчислення. Виносимо їх в окремий потік:

javascript
// worker.js onmessage = e => { while (true) {} // can run long, the UI stays responsive };

Підсумок

ТермінПояснення
Нескінченний цикл у функціїНіколи не виходить зі стека викликів
НаслідокEvent loop заблоковано, браузер «завмирає»
РішенняДілити роботу на частини (setTimeout, requestAnimationFrame, Web Workers)

Типові помилки

  • Думати, що setTimeout(fn, 0) «перерве» цикл. Колбек лише стає в чергу, а виконається тільки після звільнення стека.
  • Вважати, що промиси мають перевагу над блокуванням. Мікрозадачі теж чекають на вільний стек.
  • Виносити важкий цикл у async функцію і вважати це асинхронністю. async без await на реальній асинхронній операції виконується синхронно і так само блокує потік.
  • Ставити умову виходу, яка ніколи не стає хибною, наприклад забути інкремент лічильника або змінювати копію змінної замість самої змінної.
  • Робити нескінченну рекурсію замість циклу і чекати «зависання»: тут навпаки швидко впаде RangeError: Maximum call stack size exceeded.

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

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

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