← Назад к вопросам

Как работает SetTimeout?

1.0 Junior🔥 171 комментариев
#JavaScript Core#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

setTimeout — это один из самых основных инструментов в JavaScript для отложенного выполнения кода. Хотя на первый взгляд это просто, под капотом происходит много интересного.

Базовый синтаксис

setTimeout(callback, delay, ...args);

// Примеры:
setTimeout(() => {
  console.log("Привет!");
}, 1000); // выполнится через 1000мс (1 секунду)

setTimeout(() => {
  console.log("Hello");
}); // выполнится как можно скорее (delay = 0)

Важная концепция: Delay — это МИНИМУМ, не максимум

setTimeout гарантирует, что callback не выполнится ДО истечения задержки, но НЕ гарантирует, что он выполнится ровно в это время.

const start = Date.now();

setTimeout(() => {
  const elapsed = Date.now() - start;
  console.log(`Прошло ${elapsed}мс`); // может быть 1005, 1010, 1050мс
}, 1000);

// Если браузер занят, callback будет отложен дольше
for (let i = 0; i < 1000000000; i++) {} // тяжелое вычисление
// callback выполнится ПОСЛЕ завершения цикла

Event Loop и Macrotask Queue

setTimeout — это макротаск (macrotask). Вот как это работает:

console.log("1. Начало");

setTimeout(() => {
  console.log("3. setTimeout callback");
}, 0);

Promise.resolve()
  .then(() => {
    console.log("2. Promise микротаск");
  });

console.log("4. Конец синхронного кода");

// Порядок вывода:
// 1. Начало
// 4. Конец синхронного кода
// 2. Promise микротаск
// 3. setTimeout callback

Почему такой порядок?

  1. Выполняется весь синхронный код (1 и 4)
  2. Затем выполняются все микротаски из очереди (Promise, 2)
  3. Затем выполняется первый макротаск из очереди (setTimeout, 3)

Визуализация Event Loop

// Call Stack → Microtask Queue → Macrotask Queue

console.log("start");          // Call Stack

setTimeout(() => {              // Добавляется в Macrotask Queue
  console.log("timeout");
}, 0);

Promise.resolve()
  .then(() => {                 // Добавляется в Microtask Queue
    console.log("promise");
  });

console.log("end");             // Call Stack

// Вывод:
// start
// end
// promise
// timeout

setTimeout с delay = 0

Много разработчиков думают, что setTimeout(fn, 0) выполнится сразу, но это не так.

console.log("Начало");

setTimeout(() => {
  console.log("Это НЕ выполнится сразу!");
}, 0); // даже с 0мс задержки, callback пойдёт в Macrotask Queue

console.log("Конец");

// Вывод:
// Начало
// Конец
// Это НЕ выполнится сразу!

Минимальная задержка в браузерах

У браузеров есть минимальная задержка для setTimeout. Если указать меньше, браузер всё равно установит минимальное значение.

setTimeout(() => {
  console.log("Выполнится не раньше чем через 4мс");
}, 0); // даже если написать 0

setTimeout(() => {
  console.log("Выполнится не раньше чем через 4мс");
}, 1);

setTimeout(() => {
  console.log("Выполнится не раньше чем через 4мс");
}, 2);

setTimeout(() => {
  console.log("Это выполнится примерно через 4-5мс");
}, 4);

// После 5 уровней вложенности setTimeout, минимальная задержка = 4мс

Это называется "throttling setTimeout" и сделано для экономии ресурсов.

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

Пример 1: Батчинг обновлений

let updates = [];
let isScheduled = false;

function scheduleUpdate(value) {
  updates.push(value);
  
  if (!isScheduled) {
    isScheduled = true;
    setTimeout(() => {
      console.log("Обработка", updates);
      updates = [];
      isScheduled = false;
    }, 0); // Потом обработаем все накопленные обновления
  }
}

scheduleUpdate(1);
scheduleUpdate(2);
scheduleUpdate(3);
// Выведет: "Обработка [1, 2, 3]" одним батчем

Пример 2: UI обновления

// Плохо — блокирует UI
function slowOperation() {
  for (let i = 0; i < 1000000; i++) {
    document.getElementById('result').textContent = i;
  }
}

// Хорошо — делим работу на части
function fastOperation(data, index = 0) {
  if (index < data.length) {
    document.getElementById('result').textContent = data[index];
    setTimeout(() => fastOperation(data, index + 1), 0);
  }
}

Пример 3: Дебаунсинг

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

const handleSearch = debounce((query) => {
  console.log("Поиск:", query);
}, 300);

handleSearch("a");
handleSearch("ab");
handleSearch("abc"); // Выполнится только это

Return value: ID таймера

setTimeout возвращает ID, который можно использовать для отмены:

const timerId = setTimeout(() => {
  console.log("Этого не будет");
}, 1000);

// Отменяем выполнение
clearTimeout(timerId);
// callback никогда не выполнится

Передача аргументов

function greet(name, greeting) {
  console.log(`${greeting}, ${name}!`);
}

// До ES5
setTimeout(function() {
  greet("Alice", "Hello");
}, 1000);

// ES5+ — передаём аргументы третьим параметром
setTimeout(greet, 1000, "Alice", "Hello");
// Выведет: "Hello, Alice!" через 1 сек

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

Ошибка 1: Потеря контекста this

const obj = {
  name: "Alice",
  sayHi() {
    setTimeout(function() {
      console.log(this.name); // undefined, потеря this!
    }, 1000);
  }
};

obj.sayHi();

// Решение 1: arrow function
const obj = {
  name: "Alice",
  sayHi() {
    setTimeout(() => {
      console.log(this.name); // "Alice"
    }, 1000);
  }
};

// Решение 2: bind
setTimeout(function() {
  console.log(this.name);
}.bind(obj), 1000);

Ошибка 2: Неправильное понимание задержки

// Новичок думает: "Выполнится через 1000мс"
// На самом деле: "Добавится в очередь, выполнится не раньше чем через 1000мс"

setTimeout(() => {
  console.log("Может быть через 1005мс, если браузер занят");
}, 1000);

setTimeout vs setInterval vs requestAnimationFrame

// setTimeout — одноразовое выполнение
setTimeout(() => console.log("Once"), 1000);

// setInterval — повторное выполнение
const id = setInterval(() => console.log("Every 1000ms"), 1000);
clearInterval(id); // остановить

// requestAnimationFrame — синхронизация с обновлением экрана
requestAnimationFrame(() => {
  console.log("Выполнится перед следующей перерисовкой (~60fps = ~16.67ms)");
});

Резюме

  • setTimeout добавляет callback в Macrotask Queue
  • Callback выполнится НЕ РАНЬШЕ чем через указанную задержку
  • Все микротаски (Promise) выполняются ДО макротасков (setTimeout)
  • setTimeout с delay=0 — это способ отложить выполнение на следующий цикл Event Loop
  • Браузеры имеют минимальную задержку (~4мс после 5 уровней вложенности)
  • Используй clearTimeout() чтобы отменить уже запланированный callback
  • Будь осторожен с потерей контекста this при использовании обычных функций