setTimeout и setInterval

setTimeout и setInterval #

setTimeout откладывает однократный вызов функции; setInterval повторяет её через заданный интервал. Оба планируют колбэк в очереди задач браузера или Node.js — код после вызова таймера выполняется сразу, не дожидаясь колбэка.


setTimeout #

Синтаксис: setTimeout(callback, delayMs, ...args). Возвращает числовой id для отмены:

function showHint(message) {
  console.log(message)
}

const timerId = setTimeout(showHint, 1500, 'Продолжайте урок — вы на верном пути')

// отмена до срабатывания
clearTimeout(timerId)

delay — минимальная задержка, не гарантия точного времени: при загруженной вкладке или длинной синхронной задаче колбэк сработает позже.

console.log('старт')

setTimeout(() => console.log('таймер'), 0)

console.log('конец')
// старт → конец → таймер

Даже 0 мс ставит задачу в очередь после текущего синхронного кода.


setInterval #

Повторяет колбэк, пока не вызовут clearInterval:

let seconds = 0

const tickId = setInterval(() => {
  seconds += 1
  console.log(`Прошло ${seconds} с`)

  if (seconds >= 3) {
    clearInterval(tickId)
  }
}, 1000)

Для точного ритма «раз в N мс после завершения предыдущего шага» чаще используют цепочку setTimeout, а не setInterval — интервал не ждёт, пока колбэк закончит работу.

function pollLessonStatus(lessonId) {
  function step() {
    // запрос к API...
    setTimeout(step, 5000)
  }
  step()
}

Аргументы колбэка #

Дополнительные параметры после задержки передаются в функцию:

setTimeout(
  (title, step) => {
    console.log(`${title}: шаг ${step}`)
  },
  300,
  'JSFlow',
  2
)

Стрелки и обычные функции ведут себя одинаково; для методов объекта следите за this — при необходимости используйте bind (см. отдельный урок).


Event loop в двух словах #

JavaScript в одном потоке выполняет синхронный код до конца. Асинхронные источники (таймеры, сеть, события DOM) ставят колбэки в очереди; движок забирает их, когда стек вызовов пуст.

function loadChapter() {
  console.log('1: запросили данные')

  setTimeout(() => console.log('3: таймер сработал'), 100)

  fetch('/api/chapter/5')
    .then(() => console.log('4: ответ сети'))
    .catch(() => {})

  console.log('2: UI не блокируется')
}

Порядок «3» и «4» зависит от того, что завершится раньше — сеть или таймер. Синхронные 1 и 2 всегда идут первыми.

Таймеры — не «пауза в языке», а планирование задачи на потом. Долгие циклы и тяжёлые вычисления всё равно блокируют интерфейс.


Практика: debounce поиска #

Типичный приём — откладывать реакцию на ввод, пока пользователь не перестанет печатать:

function createDebouncedSearch(onSearch, waitMs = 400) {
  let timerId = null

  return function handleInput(query) {
    clearTimeout(timerId)
    timerId = setTimeout(() => onSearch(query), waitMs)
  }
}

const search = createDebouncedSearch((q) => {
  console.log('ищем:', q)
}, 300)

search('ф')
search('фу')
search('функции') // один вызов onSearch с "функции"

Здесь и таймер, и замыкание на timerId.


Частые ошибки #

  1. Забыли clearInterval — утечка и лишняя нагрузка после ухода со страницы.
  2. this в методе объекта — передать колбэк без привязки: setTimeout(obj.tick, 1000) потеряет контекст.
  3. Ожидание точных 1000 мс — вкладка в фоне, троттлинг браузера и длинные задачи сдвигают срабатывание.
  4. Рекурсивный setInterval без условия выхода — бесконечный цикл; всегда предусматривайте остановку.

Итог #

  • setTimeout — один отложенный вызов; setInterval — повторяющийся, пока не отменят.
  • Колбэк таймера выполняется асинхронно, после текущего синхронного кода.
  • Таймеры участвуют в event loop: задачи ждут освобождения стека.
  • Для опроса API и поиска по вводу часто комбинируют таймеры с замыканиями и clearTimeout.

См. также #