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

Как запустится setTimeout с задержкой в 1 секунду в Event Loop если 1 секунда еще не прошла?

1.8 Middle🔥 251 комментариев
#JavaScript Core

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

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

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

Как запустится setTimeout с задержкой в 1 секунду если 1 секунда еще не прошла

Поднимаем основы

Сначала нужно понять разницу между главным потоком и асинхронными операциями. JavaScript выполняется в одном потоке, но setTimeout работает по-другому благодаря браузеру.

Архитектура Event Loop

JavaScript Engine (V8)
    ↓
Main Thread (Call Stack) — выполняет синхронный код
    ↓
Web APIs (предоставляет браузер) — таймеры, сетевые запросы
    ↓
Task Queue (Макротаск очередь) — setTimeout, setInterval
Microtask Queue (Микротаск очередь) — Promises, async/await
    ↓
Event Loop — управляет очередями

Что происходит когда вы вызываете setTimeout

Шаг 1: Регистрация

console.log("Начало");
setTimeout(() => {
  console.log("Через 1 секунду");
}, 1000);
console.log("Конец");

// Вывод:
// Начало
// Конец
// (спустя 1 сек)
// Через 1 секунду

Что происходит:

  1. Начало — выполняется синхронно
  2. setTimeout()передается в Web API (браузер), основной поток не ждёт
  3. Конец — выполняется сразу же
  4. Спустя 1 сек — callback из setTimeout добавляется в Task Queue
  5. Event Loop замечает, что Call Stack пуст — добавляет callback из очереди
  6. Через 1 секунду — выполняется

Диаграмма Event Loop

Call Stack              Web APIs           Task Queue
┌──────────────┐       ┌──────────┐      ┌──────────┐
│ console.log  │       │setTimeout│      │          │
│   Начало     │       │ 1000ms   │      │          │
└──────────────┘       └──────────┘      └──────────┘
       ↓                     ↓                 ↓
setTimeout() вызван     Таймер запущен     Ждёт 1 сек
     ↓
┌──────────────┐       ┌──────────┐      ┌──────────┐
│ console.log  │       │setTimeout│      │          │
│    Конец     │       │ 990ms    │      │          │
└──────────────┘       └──────────┘      └──────────┘
     ↓
Call Stack пуст!           ↓                 ↓
     ↓                Время истекло!   callback добавлен
┌──────────────┐       ┌──────────┐      ┌──────────┐
│              │       │          │      │callback()│
│  (пусто)     │       │ 0ms      │      │          │
└──────────────┘       └──────────┘      └──────────┘
     ↑                     ↑                 ↓
Event Loop видит    Web API отправляет  Event Loop берёт
 пусть! Берет из           в очередь      и выполняет
 очереди

Ключевой момент: АСИНХРОННОСТЬ

setTimeout не блокирует основной поток. Это асинхронная операция:

const start = Date.now();

setTimeout(() => {
  console.log("Прошло", Date.now() - start, "ms");
}, 1000);

// Тяжёлые вычисления (блокируют основной поток)
for (let i = 0; i < 2000000000; i++) {}

console.log("Синхронный код завершён");

// Вывод:
// Синхронный код завершён (через ~3 сек)
// Прошло 3000+ ms

Что произошло:

  1. setTimeout(1000) зарегистрирован в Web API
  2. Синхронный for-loop выполняется ~2 сек
  3. Основной поток занят, Event Loop ждёт
  4. When for-loop завершена (3+ сек прошло)
  5. Call Stack пуст
  6. Event Loop видит callback в Task Queue
  7. Callback выполняется (прошло ~3 сек, а не 1)

Микротаски vs Макротаски

console.log("1. Синхронный код");

setTimeout(() => {
  console.log("4. setTimeout (макротаск)");
}, 0);

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

console.log("2. Ещё синхронный код");

// Вывод:
// 1. Синхронный код
// 2. Ещё синхронный код
// 3. Promise (микротаск)
// 4. setTimeout (макротаск)

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

  1. Call Stack — выполнить весь синхронный код
  2. Microtask Queue — выполнить ВСЕ микротаски (Promises, async/await)
  3. Rendering — обновить DOM
  4. Macrotask Queue — выполнить ОДИН макротаск (setTimeout, setInterval)
  5. Вернуться к шагу 2

Практический пример: задержка на 0ms

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

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

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

// Вывод:
// Начало
// Конец
// setTimeout 0

Именно поэтому setTimeout(fn, 0) используется для дефера (отложить до следующего цикла Event Loop):

// Плохо: блокирует интерфейс
function heavyComputation() {
  for (let i = 0; i < 1000000000; i++) {}
}
button.addEventListener("click", heavyComputation);

// Хорошо: не блокирует
button.addEventListener("click", () => {
  setTimeout(heavyComputation, 0);
});

Внутри Node.js (если вопрос о backend)

В Node.js setTimeout работает аналогично, но есть libuv, который управляет таймерами и асинхронностью. Главная идея та же — асинхронность не блокирует основной поток.

Вывод

setTimeout не ждёт 1 секунду в основном потоке. Вместо этого:

  1. Callback регистрируется в Web API браузера
  2. Основной поток продолжает работать (асинхронность)
  3. Спустя 1 сек Web API добавляет callback в Task Queue
  4. Когда Call Stack пуст, Event Loop берёт callback и выполняет
  5. Если основной поток занят, callback ждёт (отсюда задержки > 1 сек)

Это неблокирующая асинхронность — ключевой принцип JavaScript.