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

В чем разница между Event Loop в браузере и через Node.js?

1.0 Junior🔥 151 комментариев
#JavaScript Core

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

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

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

В чем разница между Event Loop в браузере и через Node.js?

Хотя Event Loop работает на одном и том же принципе в браузере и Node.js, их реализация существенно отличается. Браузер оптимизирован для UI рендеринга, а Node.js — для серверных операций ввода-вывода.

Основная архитектура Event Loop

Браузер (общая схема):

┌─────────────────────┐
│ Call Stack          │
└─────────────────────┘
         ↓
┌─────────────────────┐
│ Microtask Queue     │ (Promise, async/await, MutationObserver)
└─────────────────────┘
         ↓
┌─────────────────────┐
│ Render Pipeline     │ ← ТОЛЬКО В БРАУЗЕРЕ!
└─────────────────────┘
         ↓
┌─────────────────────┐
│ Macrotask Queue     │ (setTimeout, setInterval, I/O)
└─────────────────────┘

Node.js (более сложная схема):

   ┌─ timers ─────────────┐
   │ (setTimeout, setInterval)
   │
   ├─ pending callbacks ──┤
   │ (ошибки системных операций)
   │
   ├─ idle, prepare ──────┤
   │ (внутреннее использование)
   │
   ├─ poll ───────────────┤ ← ОСНОВНОЙ БЛОК
   │ (I/O callbacks: fs, network)
   │ ← здесь Event Loop может заблокироваться
   │
   ├─ check ──────────────┤
   │ (setImmediate)
   │
   └─ close callbacks ────┘
   (cleanup)

Миктотаски выполняются МЕЖДУ КАЖДОЙ ФАЗОЙ

Ключевые различия

1. Рендеринг (ТОЛЬКО в браузере)

Браузер:

console.log('1');

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

setTimeout(() => {
  console.log('setTimeout');
  // Браузер перерисует страницу перед этим макротаском!
}, 0);

// Браузер:
// 1. Выполнить синхронный код
// 2. Выполнить микротаски
// 3. ПЕРЕРИСОВАТЬ (render frame) ← ВАЖНО!
// 4. Выполнить ОДИН макротаск
// 5. К шагу 2

// Вывод:
// 1
// Promise
// [браузер перерисует страницу здесь]
// setTimeout

Node.js:

console.log('1');

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

setTimeout(() => {
  console.log('setTimeout');
  // Нет рендеринга — это серверный код!
}, 0);

// Node.js:
// 1. Выполнить синхронный код
// 2. Выполнить микротаски
// 3. (НЕ ПЕРЕРИСОВЫВАЕТ — браузера нет!)
// 4. Перейти к следующей фазе Event Loop

// Вывод:
// 1
// Promise
// setTimeout
// (никакого рендеринга)

2. Фазы в Node.js

Фаза TIMERS:

setTimeout(() => console.log('1'), 100);
setTimeout(() => console.log('2'), 50);

// Node.js выполнит сначала callback на 50ms, потом на 100ms
// Это не зависит от порядка вызовов

Фаза POLL (самая важная для I/O):

const fs = require('fs');

// Это выполнится в фазе POLL
fs.readFile('file.txt', () => {
  console.log('File read');
});

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

// Вывод:
// File read (в фазе poll)
// Timeout (в фазе timers следующей итерации)

Фаза CHECK (setImmediate):

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

setImmediate(() => console.log('setImmediate'));

// Вывод:
// setTimeout
// setImmediate

// ПОТОМУ ЧТО:
// 1. Фаза timers: setTimeout выполнится
// 2. Фаза check: setImmediate выполнится

ВАЖНО: setImmediate РАНЬШЕ setTimeout? НЕ в браузере!

// В браузере setImmediate не существует!
// setTimeout(() => {}, 0) ≈ как setImmediate в Node

3. Микротаски в разных окружениях

Браузер:

// Микротаски выполняются ПОСЛЕ каждого макротаска
setTimeout(() => {
  console.log('1');
  Promise.resolve().then(() => console.log('2'));
}, 0);

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

// Вывод:
// 1
// 2 (микротаск выполнился после первого setTimeout)
// 3 (следующий macrotask)

Node.js (то же самое, но с фазами):

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

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

// Вывод:
// 1
// 2 (микротаск выполнился)
// 3 (следующая фаза)
// 
// Результат тот же, но причины разные!

4. Разница с process.nextTick()

ТОЛЬКО в Node.js — есть специальный process.nextTick():

console.log('1');

process.nextTick(() => console.log('2'));

Promise.resolve().then(() => console.log('3'));

setTimeout(() => console.log('4'), 0);

// Вывод:
// 1
// 2 (process.nextTick выполнится ДО Promise!)
// 3 (Promise)
// 4 (setTimeout)

// process.nextTick имеет ВЫШЕ приоритет, чем микротаски!

Это работает потому что process.nextTick имеет свою очередь (nextTickQueue), которая выполняется ДО микротасков.

5. Очередь setImmediate vs process.nextTick

// В браузере этого не существует!
// ТОЛЬКО Node.js:

process.nextTick(() => console.log('nextTick'));
setImmediate(() => console.log('setImmediate'));

// Вывод:
// nextTick (выполнится первым)
// setImmediate (выполнится вторым)

6. Поведение в цикле

Браузер (оптимизирован для видимых обновлений):

for (let i = 0; i < 1000; i++) {
  setTimeout(() => {
    console.log(i);
  }, 0);
}

// Браузер выполнит несколько setTimeout,
// потом перерисует страницу,
// потом выполнит ещё несколько setTimeout
// (примерно каждые 16ms для 60fps)

Node.js (не ограничен экраном):

for (let i = 0; i < 1000; i++) {
  setTimeout(() => {
    console.log(i);
  }, 0);
}

// Node.js выполнит все 1000 вызовов setTimeout
// в фазе timers (может быть медленнее,
// но нет ограничений от рендеринга)

7. Блокирование Event Loop

Браузер:

// Долгая синхронная операция блокирует рендеринг
for (let i = 0; i < 1000000000; i++) {
  // Страница зависнет!
}
// Браузер не сможет перерисовать экран

Node.js:

const fs = require('fs');

// Синхронное чтение файла блокирует Event Loop
fs.readFileSync('file.txt');
// Ничего не может выполниться до завершения

// ПРАВИЛЬНО:
fs.readFile('file.txt', () => {
  // Асинхронно, не блокирует
});

Сравнительная таблица

АспектБраузерNode.js
РендерингДа (60fps)Нет
МикротаскиПосле каждого macrotaskМежду фазами
setImmediateНе существуетЕсть (фаза check)
process.nextTickНе существуетВыше микротасков
I/O операцииОчень ограниченыОсновной фокус
setTimeout точность~4ms минимумЗависит от нагрузки
Фазы Event Loop2 очереди (micro/macro)6 фаз + очереди

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

Браузер:

setTimeout(() => {
  // Фаза 1: macrotask
  console.log('setTimeout');
  
  // Это будет микротаской
  Promise.resolve().then(() => console.log('Promise'));
}, 0);

// Результат:
// setTimeout
// Promise
// [браузер перерисует экран]

Node.js:

setTimeout(() => {
  // Фаза timers
  console.log('setTimeout');
  
  Promise.resolve().then(() => console.log('Promise'));
}, 0);

// Результат:
// setTimeout
// Promise
// (микротаски между фазами, как и в браузере)

Заключение

Хотя концепция Event Loop одинакова, реализация принципиально отличается:

  • Браузер оптимизирован для UI и имеет рендеринг между макротасками
  • Node.js имеет 6 фаз с собственной логикой выполнения
  • process.nextTick() и setImmediate() — Node.js специфики
  • Микротаски выполняются в обоих, но с разными гарантиями

Понимание этих различий критично при написании кросс-платформенного JavaScript кода.