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

Как понимаешь когда выполнится setTimeout?

2.0 Middle🔥 131 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

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

Понимание асинхронности: когда выполнится setTimeout

setTimeout — одна из самых важных функций для понимания асинхронного JavaScript. Многие разработчики используют её, но не понимают, почему код выполняется именно в такой последовательности. Ответ лежит в Event Loop.

Event Loop: основа асинхронности

JavaScript — однопоточный язык. Но он может выполнять асинхронные операции благодаря Event Loop — механизму, который распределяет задачи в очередь.

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

setTimeout(() => {
  console.log('2. setTimeout');
}, 0);

console.log('3. Конец');

// Вывод:
// 1. Начало
// 3. Конец
// 2. setTimeout

Почему setTimeout выполнился ПОСЛЕДНИМ, хотя задержка 0 миллисекунд? Потому что setTimeout попадает в Macrotask Queue, а синхронный код — в Call Stack.

Два типа очередей

JavaScript использует две очереди:

1. Microtask Queue (приоритет выше):

  • Promises (.then, .catch, .finally)
  • async/await
  • MutationObserver
  • queueMicrotask()

2. Macrotask Queue (приоритет ниже):

  • setTimeout
  • setInterval
  • setImmediate (Node.js)
  • I/O операции
  • UI rendering
console.log('Синхронный код');

setTimeout(() => {
  console.log('setTimeout (macrotask)');
}, 0);

Promise.resolve()
  .then(() => {
    console.log('Promise (microtask)');
  });

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

// Вывод:
// Синхронный код
// Конец синхронного кода
// Promise (microtask)
// setTimeout (macrotask)

Event Loop в деталях

Порядок выполнения:

  1. Выполнить весь синхронный код (Call Stack)
  2. Выполнить все microtask'и (очередь Microtask)
  3. Выполнить один macrotask (setTimeout)
  4. Вернуться на шаг 2
┌─────────────┐
│ Call Stack  │ <- Синхронный код
└─────────────┘
       ↓
┌──────────────────┐
│ Microtask Queue  │ <- Promises, async/await
└──────────────────┘
       ↓
┌──────────────────┐
│ Macrotask Queue  │ <- setTimeout, setInterval
└──────────────────┘
       ↓
┌──────────────────┐
│ Render           │ <- Перерисовка DOM
└──────────────────┘

Пример: сложная цепочка

console.log('1. Start');

setTimeout(() => {
  console.log('7. setTimeout 1');
  Promise.resolve().then(() => {
    console.log('8. Promise в setTimeout');
  });
}, 0);

Promise.resolve()
  .then(() => {
    console.log('3. Promise 1');
    setTimeout(() => {
      console.log('6. setTimeout в Promise');
    }, 0);
  })
  .then(() => {
    console.log('4. Promise 2');
  });

setTimeout(() => {
  console.log('5. setTimeout 2');
}, 0);

console.log('2. End');

// Вывод:
// 1. Start
// 2. End
// 3. Promise 1
// 4. Promise 2
// 5. setTimeout 2 (или 7, зависит от браузера)
// 7. setTimeout 1 (или 5)
// 8. Promise в setTimeout
// 6. setTimeout в Promise

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

Пример 1: Гарантировать выполнение ПОСЛЕ рендера

// Плохо: setTimeout может запуститься до рендера
setTimeout(() => {
  // Может быть слишком рано
}, 0);

// Хорошо: requestAnimationFrame гарантирует после рендера
requestAnimationFrame(() => {
  console.log('После рендера');
});

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

function handleMultipleUpdates() {
  // React батчит обновления в одном macrotask
  setState1(prev => prev + 1);
  setState2(prev => prev + 1);
  // Оба обновления произойдут в одном рендере

  // Но если обновить в setTimeout, батчинг не сработает
  setTimeout(() => {
    setState3(prev => prev + 1); // Отдельный рендер
  }, 0);
}

Пример 3: Отмена setTimeout

const timerId = setTimeout(() => {
  console.log('Выполнится через 1 секунду');
}, 1000);

// Отменить до выполнения
clearTimeout(timerId);
console.log('Отменено'); // setTimeout никогда не выполнится

Как я объясняю это интервьюеру

  1. setTimeout не выполняется сразу, даже если задержка 0
  2. Call Stack должен быть пуст, прежде чем Event Loop посмотрит в Macrotask Queue
  3. Microtask Queue выполняется раньше Macrotask Queue
  4. После каждого macrotask может быть rendering

Это критично для написания предсказуемого и эффективного кода, особенно при работе с анимациями, обновлениями состояния и синхронизацией данных.