setTimeout и setInterval

setTimeout выполняет функцию однократно через указанную задержку. setInterval выполняет функцию периодически с указанным интервалом.

Зачем нужно

Таймеры нужны для отложенного выполнения, периодических проверок, анимаций, debounce/throttle, автосохранения.

Где используется

  • Debounce и throttle
  • Анимации (простые)
  • Периодический опрос сервера (polling)
  • Автосохранение
  • Показ/скрытие уведомлений
  • Задержка перед действием

Предпосылки

Event Loop, Callbacks

setTimeout

// Синтаксис: setTimeout(callback, delay, ...args)
const timerId = setTimeout(() => {
  console.log('Прошла 1 секунда');
}, 1000);

// С аргументами
setTimeout((name, age) => {
  console.log(`${name}, ${age} лет`);
}, 1000, 'Алиса', 25);

// Очистка таймера
clearTimeout(timerId);

// setTimeout с 0 — выполнится после текущего кода
console.log('1');
setTimeout( => console.log('2'), 0);
console.log('3');
// Вывод: 1, 3, 2

setInterval

// Синтаксис: setInterval(callback, interval, ...args)
let count = 0;
const intervalId = setInterval(() => {
  count++;
  console.log(`Тик ${count}`);

  if (count >= 5) {
    clearInterval(intervalId); // Остановить после 5 тиков
  }
}, 1000);

// Практический пример: часы
function startClock() {
  const clockId = setInterval(() => {
    const now = new Date();
    console.log(now.toLocaleTimeString());
  }, 1000);

  return clockId; // Вернуть для возможности остановки
}

const clock = startClock;
// clearInterval(clock); // Остановить часы

Рекурсивный setTimeout vs setInterval

// setInterval: интервал включает время выполнения
// Если callback занимает 200ms, а интервал 1000ms,
// то МЕЖДУ вызовами будет 800ms

setInterval(function heavyTask() {
  // Занимает 200ms
}, 1000);
// Timeline: |--200--|----800----|--200--|----800----|

// Рекурсивный setTimeout: гарантированный интервал МЕЖДУ вызовами
function recursiveTimeout() {
  // Выполняем задачу
  heavyTask; // 200ms

  // Планируем следующий вызов ПОСЛЕ завершения
  setTimeout(recursiveTimeout, 1000);
}
setTimeout(recursiveTimeout, 1000);
// Timeline: |--200--|----1000----|--200--|----1000----|

// Рекомендация: для тяжёлых задач используй рекурсивный setTimeout

// Шаблон с async/await
async function poll() {
  try {
    const data = await fetch('/api/status').then(r => r.json());
    updateUI(data);
  } catch (err) {
    console.error(err);
  }
  setTimeout(poll, 5000); // Следующий опрос через 5 секунд
}
poll;

Минимальная задержка

// Браузеры ограничивают минимальную задержку
// В активной вкладке: ~4ms (для вложенных >5 уровней)
// В фоновой вкладке: ~1000ms (для экономии ресурсов)

// Вложенные setTimeout с 0
let start = Date.now();
let times = ;

setTimeout(function run() {
  times.push(Date.now() - start);
  if (times.length < 10) {
    setTimeout(run, 0);
  } else {
    console.log(times);
    // Примерно: [1, 1, 1, 1, 4, 5, 5, 5, 5, 5]
    // Первые 4 — быстро, потом минимум ~4ms
  }
}, 0);

Практические паттерны

Debounce с setTimeout

function debounce(fn, delay) {
  let timerId;
  return function(...args) {
    clearTimeout(timerId);
    timerId = setTimeout( => fn.apply(this, args), delay);
  };
}

const handleSearch = debounce((query) => {
  fetch(`/api/search?q=${query}`);
}, 300);

input.addEventListener('input', (e) => handleSearch(e.target.value));

Промисифицированный delay

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function animate() {
  element.style.opacity = '0';
  await delay(300);
  element.style.display = 'none';
}

Таймаут для операции

function timeout(ms) {
  return new Promise((_, reject) =>
    setTimeout( => reject(new Error(`Timeout после ${ms}ms`)), ms)
  );
}

const data = await Promise.race([
  fetch('/api/data').then(r => r.json()),
  timeout(5000)
]);

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

1. this в callback

const obj = {
  name: 'Алиса',
  greet {
    setTimeout(function {
      // console.log(this.name); // undefined!
    }, 100);

    // Решения:
    setTimeout( => console.log(this.name), 100); // arrow function
    setTimeout(function { console.log(this.name); }.bind(this), 100); // bind
  }
};

2. Строка вместо функции

// Плохо (работает, но как eval — небезопасно)
setTimeout("console.log('hello')", 1000);

// Хорошо
setTimeout( => console.log('hello'), 1000);

3. Утечка памяти — забытые интервалы

// Всегда очищай при размонтировании компонента
const id = setInterval(updateData, 5000);

// При уходе со страницы / удалении компонента:
clearInterval(id);

Практика

  1. Создай обратный отсчёт от 10 до 0 с setInterval
  2. Реализуй debounce для поисковой строки
  3. Напиши промисифицированный delay(ms)
  4. Покажи разницу между setInterval и рекурсивным setTimeout

Связанные темы

Ресурсы


🎓 Источник: Timers, Timeouts, and EventEmitter in JavaScript and Node.js

  • 📅 2018-11-01 · YouTube
  • Тезисы:
    • setTimeout НЕ В спецификации языка JavaScript — это host API. В Node это библиотека timers со своими отличиями.
    • Таймер — жирный объект в Node (содержит handle, callback, ref/unref). enroll/unenroll позволяют сэкономить — выделить меньше реальных таймеров ОС.
    • Гарантия только "не раньше, чем X миллисекунд" — никаких "точно через X".
    • process.nextTick выполняется раньше setTimeout и раньше Promise.then (отдельная nextTickQueue в Node).
    • setTimeout(1) фактически срабатывает раньше setInterval(0) в типичном Node.
    • unref позволяет таймеру не держать event loop активным.
  • Цитата:

    «Пустой стек — нормальное состояние event loop. Когда стек пуст, цикл проверяет таймеры, очереди I/O, микротаски.»


⚡ Источник: sobes 08 — JavaScript interviews Timers · AsForJS

  • 📅 2023-08-28 · YouTube
  • Тезисы:
    • Таймеров нет в спецификации JS. Это полностью host-фича.
    • Правило "минимум 4 мс" применяется только для вложенности >= 5 (по WHATWG; W3C исторически говорил >5; до 2020 — двоевластие двух стандартов).
    • Хост МОЖЕТ задержать таймер на сколько угодно — таймер в 10 мс может стать 8 секундами (фоновая вкладка).
    • В Node таймеры — отдельная фаза event loop, одна из первых.
    • В Node callback setTimeout — только функция, не строка (в браузере исторически допускался eval-стиль).
    • Отрицательный timeout сбрасывается в 0.
    • В среде D8 (минимальный хост V8) setTimeout вообще игнорирует и timeout, и дополнительные аргументы.
  • Цитата:

    «К таймеру относись как к игрушке: гарантия только "не ранее, чем", сходятся все среды только на этом.»


⚡ Источник: Производительность Async Function · AsForJS

  • 📅 2025-07-08 · YouTube
  • Тезис о таймерах: даже сам Promise — лёгкая обёртка, но await каждой операции создаёт continuation (фактически приостанавливает функцию как генератор). Цена async — не в таймере, а в "заморозке" функции после каждого await.