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

Что такое Promise в рамках Event Loop?

1.3 Junior🔥 241 комментариев
#JavaScript Core

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

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

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

Что такое Promise в рамках Event Loop?

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

Что такое Promise

Promise — это объект, представляющий будущий результат асинхронной операции. У него есть три состояния:

  • pending — операция ещё выполняется
  • fulfilled — операция успешно завершена, получен результат
  • rejected — операция завершилась ошибкой
const promise = new Promise((resolve, reject) => {
  // Асинхронная операция
  setTimeout(() => {
    resolve('Успех!');
  }, 1000);
});

promise
  .then(result => console.log(result)) // Успех!
  .catch(error => console.error(error));

Event Loop: основы

Event Loop — это механизм, который постоянно проверяет, есть ли работа, и выполняет её в правильном порядке.

структура Event Loop:

┌─────────────────────────┐
│     Call Stack          │ <- Текущий код
├─────────────────────────┤
│   Microtask Queue       │ <- Promise callbacks
│   (Microtasks)          │
├─────────────────────────┤
│   Macrotask Queue       │ <- setTimeout, setInterval
│   (Macrotasks)          │
└─────────────────────────┘

Promise и Microtask Queue

Это критически важное различие:

Promises используют Microtask Queue:

  • Callbacks от .then(), .catch(), .finally() — это микротаски
  • Микротаски выполняются сразу после выполнения текущего кода
  • Но ДО выполнения следующего макротаска (setTimeout)

setTimeout использует Macrotask Queue:

  • Callback от setTimeout — это макротаска
  • Выполняется после всех микротасков

Порядок выполнения (очень важно!)

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

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

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

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

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

Почему так происходит?

  1. Event Loop выполняет весь синхронный код в Call Stack (1, 1.5)
  2. Синхронный код закончился, Stack пуст
  3. Event Loop проверяет Microtask Queue — выполняет все Promise callbacks (2, 3)
  4. Microtask Queue пуста
  5. Event Loop проверяет Macrotask Queue — выполняет setTimeout (4)

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

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

fetch('/api/data')
  .then(response => response.json())
  .then(data => {
    console.log('Данные получены:', data);
  })
  .catch(error => {
    console.error('Ошибка:', error);
  });

setTimeout(() => {
  console.log('Таймер закончился');
}, 100);

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

// Вывод:
// Начало
// Конец
// Данные получены: {...}  <- выполнится раньше, чем таймер!
// Таймер закончился

Почему Promise выполнится раньше setTimeout? Потому что Promise использует Microtask Queue, которая имеет больший приоритет!

Async/Await в контексте Event Loop

async function fetchData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;  // Это Promise!
  } catch (error) {
    console.error(error);
  }
}

fetchData()
  .then(data => console.log(data))
  .catch(error => console.error(error));

await — это синтаксический сахар над .then()

  • await паузирует выполнение async функции
  • Когда Promise разрешится, выполнение продолжается
  • Все это происходит в Microtask Queue!

Трудные случаи

Пример 1: Множественные Promise в цепочке

Promise.resolve(1)
  .then(x => Promise.resolve(x + 1))
  .then(x => Promise.resolve(x + 1))
  .then(x => console.log(x)); // 3

// Каждый .then() добавляет новую микротаску
// Все они выполняются в одном Event Loop tick'е

Пример 2: Смешивание микро- и макротасок

for (let i = 0; i < 2; i++) {
  setTimeout(() => console.log('macro', i), 0);
  Promise.resolve()
    .then(() => console.log('micro', i));
}

// Вывод:
// micro 0
// micro 1
// macro 0
// macro 1

Практическое применение во Frontend

1. Оптимизация производительности

// Плохо: блокирует UI
for (let i = 0; i < 1000; i++) {
  doHeavyWork(i);
}

// Хорошо: разбиваем на макротаски
function processInChunks(items, callback) {
  let index = 0;
  
  function process() {
    if (index < items.length) {
      callback(items[index]);
      index++;
      setTimeout(process, 0); // Следующий макротаск
    }
  }
  
  process();
}

2. Тестирование асинхронного кода

// Jest/Vitest
test('Promise должна разрешиться', async () => {
  const result = await fetchData();
  expect(result).toBe('expected');
});

// Важно: await дождётся микротаски!

Заключение

Ключевые моменты:

  1. Promise = Microtask Queue — выполняется раньше setTimeout
  2. Event Loop приоритизирует: Синхронный код → Микротаски → Макротаски
  3. Все .then() callbacks — это микротаски, выполняются в едином "батче"
  4. Понимание этого критично для оптимизации и отладки асинхронного кода