setTimeout и setInterval
setTimeout выполняет функцию однократно через указанную задержку. setInterval выполняет функцию периодически с указанным интервалом.
Зачем нужно
Таймеры нужны для отложенного выполнения, периодических проверок, анимаций, debounce/throttle, автосохранения.
Где используется
- Debounce и throttle
- Анимации (простые)
- Периодический опрос сервера (polling)
- Автосохранение
- Показ/скрытие уведомлений
- Задержка перед действием
Предпосылки
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);
Практика
- Создай обратный отсчёт от 10 до 0 с setInterval
- Реализуй debounce для поисковой строки
- Напиши промисифицированный
delay(ms) - Покажи разницу между setInterval и рекурсивным setTimeout
Связанные темы
- Event Loop
- Callbacks
- Promise
- Debounce и Throttle для DOM-событий
- Debounce и Throttle для DOM-событий
Ресурсы
🎓 Источник: 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 активным.
- setTimeout НЕ В спецификации языка JavaScript — это host API. В Node это библиотека
- Цитата:
«Пустой стек — нормальное состояние 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.