setInterval

setInterval – метод Web API, многократно вызывающий функцию с заданной задержкой в миллисекундах. Возвращает числовой id таймера; повтор останавливают вызовом clearInterval(id).

4 минуты чтения

setInterval вместе с setTimeout образует пару методов планирования вызовов в JavaScript. Формально это не часть языка, а Web API окна браузера (Window.setInterval); аналогичные таймеры есть и в Node.js. Метод решает задачу «выполнять функцию снова и снова с заданным шагом»: часы на странице, опрос API, ротация слайдов. У простых примеров есть обратная сторона, о которой молчит половина учебников: заявленный интервал не гарантирован, таймер дрейфует, а в фоновых вкладках браузеры замедляют его вплоть до одного срабатывания в минуту. Для контроля дрейфа пригодится Date.now().

Как это работает

Синтаксис: setInterval(func, delay, param1, ..., paramN). Первый аргумент – ссылка на функцию, второй – задержка в миллисекундах, дополнительные параметры передаются в колбэк при каждом вызове. Первое срабатывание происходит после первой задержки, а не сразу. Метод возвращает intervalID – положительное целое число, уникально идентифицирующее таймер. Его сохраняют, чтобы остановить повтор:

const id = setInterval(() => {
  console.log("тик", new Date().toLocaleTimeString());
}, 1000);

// остановка
clearInterval(id);

Четыре технических нюанса, которые всплывают в реальном коде:

  • Общий пул ID – идентификаторы setInterval и setTimeout берутся из одного пула: технически clearInterval отменит и таймер setTimeout, но смешивать их не стоит.
  • Максимальная задержка – delay хранится как 32-битное знаковое целое: максимум 2 147 483 647 мс (около 24,8 дня), при переполнении таймер срабатывает немедленно.
  • Клампинг 4 мс – после пяти уровней вложенности таймеров задержка меньше 4 мс принудительно поднимается до 4 мс (требование HTML-стандарта).
  • Строка вместо функцииsetInterval("doStuff()", 1000) работает как скрытый eval: риск XSS, а в окружениях с CSP и Trusted Types вызов блокируется. Передавайте только ссылку на функцию.

Почему таймер «плывёт» и чем это лечить

setInterval не ждёт завершения колбэка – запуски идут по расписанию. Время работы колбэка съедает часть интервала, поэтому реальная пауза между вызовами меньше заявленной. Если колбэк выполняется дольше delay, вызовы идут вплотную друг к другу. Добавьте event loop: функция запустится, только когда стек свободен, так что точная задержка не обещается в принципе – влияют нагрузка на процессор и другие задачи. Дрейф подтверждён и в Node.js (issue nodejs/node#21822).

Лекарство – рекурсивный setTimeout. Пауза отсчитывается от конца одного вызова до начала следующего, наложения исключены, а интервал можно менять на лету. MDN прямо рекомендует этот паттерн, когда логика может выполняться дольше интервала:

function poll() {
  heavyOperation();       // занимает разное время
  setTimeout(poll, 1000); // ровно 1 с после завершения
}
poll();
setInterval, рекурсивный setTimeout и requestAnimationFrame
СпособПауза между вызовамиНаложения вызововТиповые задачи
setIntervalПо расписанию; колбэк съедает часть интервалаВозможны при долгом колбэкеЧасы, простой поллинг, слайдеры
Рекурсивный setTimeoutОт конца вызова до начала следующегоИсключеныОпрос API, тяжёлые колбэки, динамический интервал
requestAnimationFrameСинхронно с перерисовкой (обычно 60 кадров/с)Кадр за кадромАнимации; автопауза в фоновой вкладке

Зачем это нужно

setInterval уместен там, где странице нужен регулярный «пульс»: обратный отсчёт до конца акции, обновление времени, периодическая проверка статуса заказа или платежа. Но у метода есть три ловушки, которые проявляются уже в продакшене.

Троттлинг фоновых вкладок. Firefox замедляет таймеры неактивных вкладок до минимум 1 секунды (кроме страниц с активным AudioContext). Chrome в фоне проверяет таймеры раз в секунду, а начиная с Chrome 88 (январь 2021) включает intensive-режим – раз в минуту, когда совпали четыре условия: вкладка в фоне дольше 5 минут, цепочка вложенных таймеров достигла 5, страница не издавала звук минимум 30 секунд, WebRTC не используется. Часы «раз в секунду» в фоновой вкладке физически не будут тикать чаще раза в минуту – это нужно учитывать в логике.

Утечки памяти. Пока интервал не отменён, движок держит ссылку на колбэк, а колбэк – своё замыкание с внешними переменными. Забытый setInterval в компоненте SPA или виджете – классическая утечка: очищайте интервал при размонтировании (cleanup-функция в useEffect, beforeUnmount и аналоги).

Потеря this. Метод, переданный напрямую (setInterval(counter.count, 1000)), теряет this – в строгом режиме это TypeError. Решения: стрелочная обёртка () => counter.count() или counter.count.bind(counter).

Когда setInterval не нужен вовсе. Анимации – зона requestAnimationFrame: он синхронизирован с перерисовкой и сам приостанавливается в фоне, тогда как интервал даёт рассинхрон с кадрами. Опрос ширины окна «каждые N миллисекунд» заменяется подпиской на события matchMedia, а опрос состояния DOM после действий пользователя – событийной моделью и делегированием событий. Правило: если существует событие, интервал не нужен.

Пример

Поллинг API с остановкой по условию – типовой продакшен-сценарий: страница оплаты раз в 5 секунд спрашивает бэкенд о статусе платежа и глушит таймер, получив ответ.

const id = setInterval(async () => {
  const res = await fetch("/api/status");
  const { done } = await res.json();
  if (done) clearInterval(id);
}, 5000);

Если важна точность на длинной дистанции (обратный отсчёт, секундомер), дрейф компенсируют сверкой с Date.now(): каждый шаг вычисляет накопившееся отставание и сокращает следующую паузу.

const start = Date.now();
let tick = 0;
function step() {
  tick++;
  const drift = Date.now() - start - tick * 1000;
  setTimeout(step, Math.max(0, 1000 - drift));
}
setTimeout(step, 1000);

Такой самокорректирующийся таймер держит ровный шаг даже при загруженном event loop – в отличие от «голого» setInterval, который со временем накапливает отставание.

Частые вопросы

Как остановить setInterval?

Сохранить возвращаемый идентификатор и передать его в clearInterval: const id = setInterval(fn, 1000); clearInterval(id). Идентификатор – положительное целое число, уникальное для таймера. Если id потерян, штатного способа остановить конкретный интервал нет, поэтому переменную с id держите в доступной области видимости.

Почему setInterval срабатывает не ровно через заданный интервал?

Метод не ждёт завершения колбэка: время его выполнения съедает часть интервала, и реальная пауза между вызовами меньше заявленной. Плюс event loop – колбэк запустится, только когда стек свободен, а нагрузка на процессор и фоновый режим вкладки добавляют задержку. Поэтому точный интервал setInterval не гарантирует никогда.

Чем рекурсивный setTimeout лучше setInterval?

Он отсчитывает паузу от конца одного вызова до начала следующего, поэтому наложения вызовов исключены даже при долгом колбэке. Интервал можно менять динамически при каждом планировании. MDN рекомендует именно этот паттерн, когда логика может выполняться дольше заданной задержки.

Почему setInterval замедляется в фоновой вкладке?

Браузеры экономят батарею и процессор. Firefox поднимает минимальную задержку неактивной вкладки до 1 секунды (исключение – страницы с активным AudioContext). Chrome в фоне проверяет таймеры раз в секунду, а с версии 88 включает intensive-режим с проверкой раз в минуту, если вкладка в фоне дольше 5 минут, цепочка таймеров достигла 5, звука нет минимум 30 секунд и не используется WebRTC.

Почему this внутри колбэка setInterval становится undefined?

Если передать метод объекта напрямую – setInterval(counter.count, 1000) – функция вызовется без привязки к объекту, и в строгом режиме this будет undefined (обращение к свойствам даст TypeError). Решения: стрелочная обёртка () => counter.count() или явная привязка counter.count.bind(counter).

Может ли setInterval вызвать утечку памяти?

Да. Пока интервал не отменён clearInterval, движок хранит ссылку на колбэк, а тот удерживает своё замыкание со всеми внешними переменными. Забытые интервалы в SPA-компонентах и виджетах – классическая утечка: всегда очищайте таймер при размонтировании компонента или закрытии виджета.

Что использовать для анимаций вместо setInterval?

requestAnimationFrame: он синхронизирован с циклом перерисовки браузера (обычно 60 кадров в секунду) и автоматически приостанавливается в фоновых вкладках. setInterval для анимаций даёт рассинхрон с кадрами и продолжает жечь ресурсы в фоне, пока браузер его не затроттлит.

Материалы по теме

Валентина Меланина

Нужна консультация?

Разберу ваш сайт и покажу точки роста

Если хотите понять, как этот термин применить к вашему проекту — начнём с аудита.