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

В чем разница между микрозадачами и макрозадачами в Node.js?

2.0 Middle🔥 111 комментариев
#Node.js и JavaScript

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

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

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

В чем разница между микрозадачами и макрозадачами в Node.js?

Это фундаментальный вопрос о том, как работает Event Loop в Node.js. Микротасики (microtasks) и макротасики (macrotasks) определяют порядок выполнения асинхронного кода и критически важны для понимания асинхронности.

Определение

Микротасики (Microtasks) — это задачи с высоким приоритетом, которые выполняются в конце текущей фазы Event Loop, перед тем как перейти к следующей фазе.

Макротасики (Macrotasks) — это задачи с низким приоритетом, которые выполняются в одной из фаз Event Loop.

Event Loop в Node.js

┌─────────────────────────────────┐
│      Event Loop Фазы            │
├─────────────────────────────────┤
│ 1. timers (setTimeout, setInterval) │
│    ↓ (выполнить все microtasks)  │
│                                 │
│ 2. pending callbacks            │
│    ↓ (выполнить все microtasks)  │
│                                 │
│ 3. idle, prepare                │
│                                 │
│ 4. poll (I/O callbacks)         │
│    ↓ (выполнить все microtasks)  │
│                                 │
│ 5. check (setImmediate)         │
│    ↓ (выполнить все microtasks)  │
│                                 │
│ 6. close callbacks              │
│    ↓ (выполнить все microtasks)  │
└─────────────────────────────────┘

Микротасики

Источники микротасик:

  • Promise.then(), Promise.catch(), Promise.finally()
  • async/await (которые преобразуются в Promises)
  • MutationObserver (в браузере)
  • queueMicrotask()
  • process.nextTick() (специфично для Node.js, выполняется ЕЩЁ раньше)
console.log('1. Синхронный код');

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

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

process.nextTick(() => {
  console.log('2. process.nextTick (ещё раньше микротаски)');
});

setImmediate(() => {
  console.log('6. setImmediate (макротаска в фазе check)');
});

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

// Вывод:
// 1. Синхронный код
// 4. Ещё синхронный код
// 2. process.nextTick
// 3. Promise.then
// 5. setTimeout
// 6. setImmediate

Макротасики

Источники макротасик:

  • setTimeout() и setInterval() — фаза timers
  • setImmediate() — фаза check
  • I/O операции (file system, network) — фаза poll
  • requestAnimationFrame() (в браузере)
console.log('Начало');

setTimeout(() => {
  console.log('Таймер 1');
}, 0);

setTimeout(() => {
  console.log('Таймер 2');
}, 0);

setImmediate(() => {
  console.log('Immediate 1');
});

setImmediate(() => {
  console.log('Immediate 2');
});

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

// Вывод:
// Начало
// Конец
// Таймер 1  (выполняются оба setTimeout как одна фаза)
// Таймер 2
// Immediate 1  (выполняются оба setImmediate как одна фаза)
// Immediate 2

Детальный пример с async/await

async function processData() {
  console.log('1. В асинх функции');
  
  await Promise.resolve();
  console.log('3. После await (это микротаска)');
}

console.log('0. Старт');

processData();

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

console.log('2. После вызова асинч функции');

// Вывод:
// 0. Старт
// 1. В асинх функции
// 2. После вызова асинч функции
// 3. После await (это микротаска)
// 4. setTimeout (макротаска)

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

ХарактеристикаМикротасикиМакротасики
ПримерыPromise, async/awaitsetTimeout, setImmediate
ПриоритетВысокийНизкий
ВыполнениеСразу после текущей фазыВ отдельной фазе
ПорядокВСЕ микротасики перед следующей фазойПо одной в фазе
process.nextTickВыполняется ещё раньшеНе применимо

Практический пример: очередь запросов

class RequestQueue {
  async processRequests(urls) {
    const results = [];
    
    // Все Promise.then выполнятся как микротасики
    for (const url of urls) {
      const response = await fetch(url);
      results.push(response); // Микротаска
    }
    
    return results;
  }
  
  scheduleCleanup() {
    // Очистка в следующей фазе (макротаска)
    setImmediate(() => {
      this.cleanup();
    });
  }
}

Проблема: микротасики могут заморозить Event Loop

// ОПАСНЫЙ КОД!
function infiniteMicrotasks() {
  Promise.resolve()
    .then(() => infiniteMicrotasks()); // Бесконечный цикл микротасик!
}

infiniteMicrotasks();
// setTimeout НИКОГДА не выполнится!
setTimeout(() => {
  console.log('Это никогда не выведется');
}, 0);

Профилирование Event Loop

const perf = require('perf_hooks');

const obs = new perf.PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    console.log(`${entry.name}: ${entry.duration}ms`);
  });
});

obs.observe({entryTypes: ['measure', 'function']});

// Регистрируем выполнение функции
perf.measure('start', undefined);

// Микротаски
Promise.resolve().then(() => {
  perf.measure('microtask', 'start');
});

// Макротаски
setTimeout(() => {
  perf.measure('macrotask', 'start');
}, 0);

Ключевые выводы

  1. Порядок: process.nextTick → Promises → setTimeout → setImmediate
  2. Все микротасики выполняются перед переходом к следующей макротаске
  3. Бесконечные микротасики могут заблокировать Event Loop
  4. process.nextTick() — специфично для Node.js, имеет приоритет выше микротасик
  5. Понимание Event Loop критично для отладки асинхронных проблем

Практическое применение: если тебе нужна задача с высоким приоритетом — используй Promise/async-await, если низким — setTimeout/setImmediate.