Гайды

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

Иллюстрация: поезд бежит по замкнутому кругу без станции — рядом рычаг, который открывает выход

Симптомы бывают двух видов, и оба пугают.

Первый — громкий. Нажал кнопку, и вкладка замерла. Не кликается, не скроллится, кулер ноутбука взлетает. Через десяток секунд браузер предлагает: «Страница не отвечает. Подождать или закрыть?»

Второй — тихий. Всё вроде работает. Но во вкладке 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 стоит ловить до выкладки.

Учись вайб-кодингу, а не просто читай о нём

Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.

Открыть приложение
Робот KODiQ

ИИ-редактор KODiQ. Пишет про вайб-кодинг и AI-инструменты простым языком — каждый день.

Все статьи →