Почему цикл не останавливается — и вкладка зависает намертво

Симптомы бывают двух видов, и оба пугают.
Первый — громкий. Нажал кнопку, и вкладка замерла. Не кликается, не скроллится, кулер ноутбука взлетает. Через десяток секунд браузер предлагает: «Страница не отвечает. Подождать или закрыть?»
Второй — тихий. Всё вроде работает. Но во вкладке Network бегут запросы — сотни, тысячи. Сервер получает их без остановки, а счёт за API растёт.
Оба — бесконечный цикл. Код крутится по кругу и не находит выхода. Цикл обязан на каком-то шаге остановиться — а здесь условие остановки никогда не наступает. Ниже три причины, которые встречаются чаще всего. Первая — самая частая в коде от ИИ, и выглядит она вообще не как цикл.
Причина 1: useEffect, который перезапускает сам себя
Если у тебя React, начни отсюда. Это тот самый «тихий» симптом.
useEffect — это код, который React запускает после отрисовки экрана. Если внутри он меняет состояние, экран перерисовывается. После перерисовки снова запускается эффект. Он снова меняет состояние. И так без конца.
useEffect(() => {
fetch("/api/orders")
.then(r => r.json())
.then(data => setOrders(data))
}) // нет массива зависимостей — запускается после КАЖДОЙ отрисовки
Как проверить. Открой вкладку Network в инструментах разработчика. Если один и тот же запрос повторяется без остановки — это он. Иногда React сам подсказывает в консоли: Maximum update depth exceeded.
Как починить. Добавь вторым аргументом массив зависимостей. Пустой [] означает «запусти один раз, при появлении экрана»:
useEffect(() => {
fetch("/api/orders")
.then(r => r.json())
.then(data => setOrders(data))
}, [])
Хитрый вариант той же беды: в массиве зависимостей стоит объект, который создаётся заново при каждой отрисовке. Для React это каждый раз «новая» вещь — и эффект снова перезапускается. Лечится тем, что в зависимости кладут простое значение: user.id, а не весь user.
Причина 2: условие, которое никогда не станет ложным
Классика и главный виновник «громкого» зависания. Цикл while крутится, пока условие верно. Если внутри ничего не меняет это условие — он будет крутиться вечно.
let page = 1
while (page <= totalPages) {
loadPage(page)
// забыли page++ — page навсегда равен 1
}
Родственники этой ошибки: счётчик идёт не в ту сторону (i-- вместо i++), или условие сравнивает не с тем числом.
Как проверить. Когда вкладка висит, открой инструменты разработчика и нажми паузу в разделе Sources (клавиша F8). Браузер остановит код ровно на той строчке, где он крутится. Посмотри на условие цикла и найди, что должно его менять.
Как починить. Добавь изменение, которое приближает выход: page++. Для страховки полезен предохранитель — максимум шагов, после которого цикл выходит в любом случае:
let guard = 0
while (page <= totalPages && guard < 1000) {
loadPage(page)
page++
guard++
}
Причина 3: список растёт, пока ты по нему идёшь
Реже, но коварнее. Цикл идёт по массиву, а внутри что-то добавляет в этот же массив. Каждый шаг двигает финишную черту дальше.
for (let i = 0; i < tasks.length; i++) {
if (tasks[i].repeat) {
tasks.push({ ...tasks[i] }) // длина растёт на каждом шаге
}
}
Как проверить. Посмотри внутрь цикла: есть ли там push, unshift или любое добавление в тот самый список, по которому идёт цикл.
Как починить. Складывай новое в отдельный список, а потом соединяй:
const extra = []
for (const task of tasks) {
if (task.repeat) extra.push({ ...task })
}
const all = [...tasks, ...extra]
Как искать, если непонятно, какая причина
Иди по порядку. Вкладка Network полна одинаковых запросов — причина 1. Вкладка намертво зависла — нажми паузу и смотри на while и for на той строке — причины 2 и 3. А если нужен помощник, покажи ИИ ровно ту строку, где встала пауза, и спроси: «что в этом цикле должно менять условие выхода?» Это вопрос с конкретным ответом, а не «почини зависание».
Почему цикл завис у меня, а у ИИ в примере работал?
Чаще всего дело в данных. На тестовом списке из трёх элементов цикл успевает закончиться. На реальных данных условие выхода может просто не совпасть — например, цикл ждёт статус "done", а сервер присылает "completed".
Бесконечный цикл может что-то сломать?
Вкладку браузера — да, её придётся закрыть. Но если цикл шлёт запросы, он может съесть лимит API или деньги на платном сервисе. Поэтому причину 1 стоит ловить до выкладки.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





