Нескінченний цикл
Якщо у функції є нескінченний цикл, JavaScript ніколи не звільнить стек викликів, і event loop зупиниться: жодне інше завдання, ані таймери, ані проміси, ані кліки, ані відмальовування інтерфейсу, не зможе виконатися. Для користувача це виглядає як повністю «зависла» сторінка.
Теорія
TL;DR
- Нескінченний цикл не дає функції завершитися, тому вона назавжди залишається в стеку викликів (call stack).
- Поки стек зайнятий, event loop не може взяти нову задачу з черги.
- Не спрацюють ані
setTimeout, аніPromise.then, ані обробники подій. - Браузер перестає перемальовувати UI і згодом пропонує закрити вкладку.
- Рішення: розбивати роботу на частини (
setTimeout,requestAnimationFrame) або виносити обчислення у Web Worker.
Швидкий приклад
function infinite() {
while (true) {
console.log('forever...');
}
}
infinite();Що відбувається крок за кроком
- Функція
infinite()потрапляє в call stack. - Починається виконання циклу
while (true), і він ніколи не завершиться. - Рушій ніколи не вийде з функції, а отже:
- стек ніколи не звільниться,
- event loop не зможе взяти наступну задачу,
- браузер перестане реагувати на кліки, скрол, а іноді «зависають» навіть DevTools.
Чому це відбувається
JavaScript це однопотокова мова. У кожен момент часу вона може виконувати лише один шматок коду, той, що зараз перебуває у call stack.
Поки функція не завершилась, рушій не перемкнеться на інші задачі: ані на setTimeout, ані на Promise.then. Черга завдань просто накопичується і чекає на вільний стек, якого вже не буде.
Таймер, який ніколи не спрацює
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
function loop() {
console.log('iteration');
setTimeout(loop, 0); // lets the engine breathe between iterations
}
loop();Варіант 2: requestAnimationFrame, підходить для візуальних задач і анімацій:
function animate() {
// draw a frame
requestAnimationFrame(animate);
}
animate();Варіант 3: Web Worker, якщо потрібні важкі обчислення. Виносимо їх в окремий потік:
// 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.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.