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

Что будет происходить в Event Loop, если запустить функцию с таймером?

1.7 Middle🔥 272 комментариев
#JavaScript Core

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

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

Обзор работы Event Loop с таймером

Когда мы запускаем функцию с таймером (например, через setTimeout или setInterval), в Event Loop добавляется асинхронная операция, которая обрабатывается по определенному алгоритму. Рассмотрим этот процесс детально.

Фазы Event Loop в Node.js и браузере

Event Loop состоит из нескольких фаз (в Node.js их 6, в браузере — несколько микрозадач и макрозадач):

  1. Timers Phase — выполнение колбэков setTimeout и setInterval
  2. Pending Callbacks — выполнение отложенных колбэков (например, системные операции)
  3. Poll Phase — обработка I/O событий и новых колбэков
  4. Check Phase — выполнение setImmediate
  5. Close Callbacks — закрытие ресурсов (например, socket.on('close', ...))

Что происходит при запуске setTimeout

console.log('Start');

setTimeout(() => {
  console.log('Timeout callback');
}, 1000);

console.log('End');

Последовательность выполнения:

  1. Синхронный код выполняется сразу:

    • console.log('Start') — попадает в Call Stack и выполняется
    • setTimeout — регистрируется в Web APIs (браузер) или Timers Module (Node.js)
    • console.log('End') — выполняется
  2. Таймер устанавливается:

    // Внутренний процесс регистрации таймера
    // 1. Таймер регистрируется с временной меткой
    const timerId = timerModule.setTimer(callback, delay, args);
    
    // 2. Колбэк помещается в очередь таймеров после задержки
    
  3. Event Loop продолжает работу:

    • Пока Call Stack пуст, Event Loop проверяет очереди
    • Сначала выполняются все микрозадачи (Promises, queueMicrotask)
    • Затем переходит к макрозадачам (таймеры, I/O, рендеринг)
  4. Когда таймер истекает:

    • Колбэк помещается в Callback Queue (браузер) или Timers Queue (Node.js)
    • Event Loop переносит колбэк в Call Stack, когда он пуст

Детали реализации и особенности

// Пример с несколькими таймерами
setTimeout(() => {
  console.log('Timeout 1');
  Promise.resolve().then(() => console.log('Promise 1'));
}, 0);

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

console.log('Sync code');

Вывод будет:

Sync code
Timeout 1
Promise 1
Timeout 2

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

  • Сначала выполняется весь синхронный код
  • Затем Event Loop обрабатывает первый таймер
  • После колбэка таймера выполняются все микрозадачи (Promise)
  • Затем обрабатывается второй таймер

Критические аспекты работы таймеров

1. Нет гарантии точного времени

setTimeout(() => {
  console.log(`Прошло ${Date.now() - start} мс`);
}, 100);

const start = Date.now();
// Долгая синхронная операция
while (Date.now() - start < 500) {}

// Таймер выполнится только через ~500мс, а не через 100мс

2. Минимальная задержка

  • В браузере: 4мс для вложенных таймеров (после 5 уровня вложенности)
  • В Node.js: 1мс минимальная задержка
  • Если указать 0, колбэк выполнится в следующем тике Event Loop

3. Проблемы с производительностью

// ПЛОХО: создает "лестницу" выполнения
for (let i = 0; i < 5; i++) {
  setTimeout(() => {
    heavyOperation(i);
  }, i * 100);
}

// ЛУЧШЕ: использовать requestAnimationFrame или Web Workers для тяжелых операций

Различия между браузером и Node.js

АспектБраузерNode.js
Очередь таймеровTask QueueTimers Queue
ПриоритетНиже микрозадачНиже микрозадач
ТочностьЗависит от вкладки/окнаБолее точная
Минимальная задержка4мс (после вложенности)1мс

Практические рекомендации

  1. Используйте setTimeout для:

    • Разделения долгих операций на части
    • Отложенного выполнения
    • Дебаунсинга и троттлинга событий
  2. Избегайте:

    • Создания тысяч таймеров одновременно
    • Вложенных таймеров с маленькой задержкой
    • Использования таймеров для точной анимации (лучше requestAnimationFrame)
  3. Альтернативы:

    // Для анимации
    requestAnimationFrame(() => {
      // Плавная анимация
    });
    
    // Для фоновых задач
    setImmediate(() => {
      // Выполнится сразу после I/O операций
    });
    
    // Для микрооптимизаций
    queueMicrotask(() => {
      // Выполнится до следующего рендеринга
    });
    

Вывод

Таймеры в JavaScript — это мощный механизм асинхронного выполнения, который интегрируется в Event Loop через отдельные очереди. Понимание их работы помогает писать эффективный, неблокирующий код и избегать распространенных ошибок, связанных с асинхронностью и производительностью. Ключевое правило: таймеры гарантируют минимальную задержку, но не гарантируют точное время выполнения, так как зависят от состояния Call Stack и других задач в Event Loop.