$ sudo teach IT
МОДУЛЬ 10 · УРОК 2

setTimeout, setInterval и колбэки

Первые инструменты асинхронности в JavaScript — таймеры. Они позволяют выполнить код через определённое время или повторять его с интервалом. Разберём как они работают, и почему злоупотребление ими — путь к «callback hell».

⏱ ~20 минут 🎓 Новичок ⚡ Асинхронность

⏱️ setTimeout — выполнить один раз через N миллисекунд

setTimeout(функция, задержка) — запускает функцию один раз через указанное количество миллисекунд. 1000 мс = 1 секунда. Браузер ставит таймер, продолжает выполнять остальной код, и когда таймер истечёт — вызывает нашу функцию.

// Через 2 секунды выводим сообщение
setTimeout(() => {
  console.log('2 секунды прошло!');
}, 2000);

console.log('Этот код выполнится РАНЬШЕ, хотя написан после!');

// Вывод:
// Этот код выполнится РАНЬШЕ, хотя написан после!
// (через 2 сек) 2 секунды прошло!

⚠️ Важно: setTimeout не «ставит программу на паузу». Браузер продолжает выполнять следующие строки кода. Колбэк вызовется позже, когда Call Stack опустеет.

setTimeout возвращает ID таймера — число. С помощью него можно отменить таймер до истечения:

const timerId = setTimeout(() => {
  console.log('Это НЕ выведется');
}, 5000);

// Отменяем таймер до того, как он сработал
clearTimeout(timerId);
console.log('Таймер отменён');

🔄 setInterval — повторять каждые N миллисекунд

setInterval(функция, интервал) — вызывает функцию снова и снова с указанным интервалом. В отличие от setTimeout, который срабатывает один раз, setInterval продолжает работать, пока его не остановишь.

let count = 0;

const intervalId = setInterval(() => {
  count++;
  console.log('Прошло секунд:', count);

  if (count >= 5) {
    clearInterval(intervalId); // останавливаем после 5 итераций
    console.log('Готово!');
  }
}, 1000);

🚨 Всегда останавливай setInterval! Если не вызвать clearInterval(), таймер будет работать вечно — даже после ухода со страницы (в SPA). Это приводит к утечкам памяти.

setTimeout

Один раз через N мс
Отменить: clearTimeout(id)

setInterval

Повторять каждые N мс
Остановить: clearInterval(id)

💻 Практические примеры

⏳ Таймер обратного отсчёта

let seconds = 10;
const display = document.querySelector('#countdown');

display.textContent = seconds;

const timer = setInterval(() => {
  seconds--;
  display.textContent = seconds;

  if (seconds <= 0) {
    clearInterval(timer);
    display.textContent = 'Время вышло!';
  }
}, 1000);

🕐 Живые часы

function updateClock() {
  const now = new Date();
  const hours = String(now.getHours()).padStart(2, '0');
  const mins = String(now.getMinutes()).padStart(2, '0');
  const secs = String(now.getSeconds()).padStart(2, '0');

  document.querySelector('#clock').textContent =
    `${hours}:${mins}:${secs}`;
}

// Сразу показываем, потом обновляем каждую секунду
updateClock();
setInterval(updateClock, 1000);

😱 Callback Hell — ад вложенных колбэков

Когда одна асинхронная операция должна следовать за другой, возникает соблазн вложить колбэк в колбэк. Три-четыре уровня вложенности — и код превращается в «рождественскую ёлку» или «ад колбэков»:

// Плохо: callback hell
setTimeout(() => {
  console.log('Шаг 1');
  setTimeout(() => {
    console.log('Шаг 2');
    setTimeout(() => {
      console.log('Шаг 3');
      setTimeout(() => {
        console.log('Шаг 4'); // ...и так далее
      }, 1000);
    }, 1000);
  }, 1000);
}, 1000);

💡 Именно для решения этой проблемы в JavaScript появились Промисы (Promise) и синтаксис async/await. Они превращают «ёлку» в линейный читаемый код. Разберём их в следующих уроках.

Callback hell — это не просто эстетическая проблема. Такой код: сложно читать, сложно дебажить, сложно добавлять обработку ошибок, сложно тестировать. Именно поэтому современный JS-код почти не использует глубокую вложенность колбэков.

✅ Итоги урока

  • setTimeout(fn, ms) — выполнить функцию один раз через ms миллисекунд
  • setInterval(fn, ms) — повторять функцию каждые ms миллисекунд
  • clearTimeout(id) и clearInterval(id) — отменить/остановить таймер
  • setTimeout не блокирует выполнение — остальной код продолжает работать
  • Всегда сохраняй ID таймера и останавливай setInterval — иначе утечка памяти
  • Callback Hell — глубокая вложенность колбэков — делает код нечитаемым
  • Для решения callback hell в JS появились Promise и async/await

В следующем уроке разберём Промисы — элегантный способ работы с асинхронным кодом. 🚀

setTimeout, setInterval и колбэки

7 вопросов

Счётчик с остановкой

Реализуйте функцию createCounter(start, interval, limit), которая каждые interval мс выводит в консоль текущее значение счётчика (начиная с start), увеличивая его на 1. Когда значение превышает limit — таймер останавливается. Функция должна возвращать объект c методом stop(), который немедленно останавливает счётчик.

Последовательность через setTimeout

Реализуйте функцию sequence(delays, fn), которая принимает массив задержек delays и функцию fn. fn должна вызываться последовательно: после первой задержки, затем после второй, и так далее. Например, sequence([1000, 500, 2000], cb) вызовет cb через 1с, затем ещё через 0.5с, затем ещё через 2с.