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

Как браузер понимает, когда можно брать задачу из стека?

2.0 Middle🔥 111 комментариев
#Браузер и сетевые технологии

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

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

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

Как браузер понимает, когда можно брать задачу из стека?

Этот вопрос про Event Loop (цикл событий) в JavaScript — механизм, который контролирует выполнение кода и обработку асинхронных операций. Event Loop постоянно проверяет, когда можно выполнить следующую задачу.

Основные очереди в Event Loop

JavaScript использует несколько очередей для управления задачами:

┌─────────────────────────────────────────┐
│        Call Stack (Стек вызовов)        │  Текущий выполняющийся код
└─────────────────────────────────────────┘
                    ↓ (когда пустой)
┌─────────────────────────────────────────┐
│      Microtask Queue (Микротаски)       │  Promise.then(), queueMicrotask()
├─────────────────────────────────────────┤
│      Macrotask Queue (Макротаски)       │  setTimeout(), setInterval(), fetch()
└─────────────────────────────────────────┘

Правила Event Loop

1. Call Stack всегда имеет приоритет

Event Loop проверяет в следующем порядке:

  1. Если Call Stack не пустой - выполнять код в нём
  2. Если Call Stack пустой - проверить Microtask Queue
  3. Если Microtask Queue не пустой - выполнить ВСЕ микротаски
  4. Если Microtask Queue пустой - выполнить одну макротаску
  5. После макротаски - снова проверить Microtask Queue
console.log('1. Start');  // Call Stack

setTimeout(() => {
  console.log('4. setTimeout');
}, 0);  // Macrotask

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

console.log('5. End');  // Call Stack

// Порядок вывода:
// 1. Start
// 5. End
// 2. Promise
// 3. Promise 2
// 4. setTimeout

Микротаски (Microtasks)

Что входит в Microtask Queue:

  • Promise.then(), .catch(), .finally()
  • queueMicrotask(callback)
  • MutationObserver

Особенность: Все микротаски выполняются ДО следующей макротаски:

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

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

queueMicrotask(() => console.log('MicroTask 3'));

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

// Порядок:
// MicroTask 1
// MicroTask 2
// MicroTask 3
// MacroTask 1
// MacroTask 2

Макротаски (Macrotasks)

Что входит в Macrotask Queue:

  • setTimeout()
  • setInterval()
  • setImmediate()
  • fetch()
  • requestAnimationFrame() (спорно, зависит от браузера)
  • Обработчики событий (click, load и т.д.)

Особенность: Выполняется одна за раз, потом Event Loop проверяет микротаски:

setTimeout(() => {
  console.log('MacroTask 1');
  Promise.resolve().then(() => console.log('MicroTask inside Macro'));
}, 0);

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

// Порядок:
// MacroTask 1
// MicroTask inside Macro  <- Микротаски выполняются сразу после макротаски
// MacroTask 2

Детальный пример Event Loop

console.log('Script start');

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

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

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

console.log('Script end');

// Пошагово:
// 1. Call Stack: выполнить синхронный код
//    -> "Script start"
//    -> "Script end"
// 2. Microtask Queue: все Promise и queueMicrotask
//    -> "Promise 1"
//    -> "queueMicrotask"
//    -> "Promise 2"
// 3. Macrotask Queue: setTimeout
//    -> "setTimeout 1"
// 4. Проверить Microtask Queue после макротаски
//    -> "Promise in setTimeout"
// 5. Macrotask Queue: второй setTimeout
//    -> "setTimeout in Promise"

// Полный порядок:
// Script start
// Script end
// Promise 1
// queueMicrotask
// Promise 2
// setTimeout 1
// Promise in setTimeout
// setTimeout in Promise

Практический пример: Когда браузер перерисовывается?

// Браузер перерисовывает между макротасками

const box = document.getElementById('box');

// 1. Макротаска
setTimeout(() => {
  box.style.background = 'red';
  // Сразу же микротаска НЕ вызывает перерисовку
  Promise.resolve().then(() => {
    console.log('Microtask: background уже красный');
  });
}, 0);

// 2. Вторая макротаска - ЗДЕСЬ произойдёт перерисовка между ними
setTimeout(() => {
  box.style.background = 'blue';
  console.log('Вторая макротаска');
}, 0);

// Результат:
// 1. Первая макротаска: красный фон
// Microtask: background уже красный
// 2. ПЕРЕРИСОВКА (box становится красным)
// 3. Вторая макротаска: синий фон
// 4. ПЕРЕРИСОВКА (box становится синим)

Как использовать знания о Event Loop?

1. Используй Promise вместо setTimeout для срочных задач

// Выполнится раньше
Promise.resolve().then(() => console.log('Urgent'));

// Выполнится позже
setTimeout(() => console.log('Not urgent'), 0);

2. Группируй операции DOM в одну макротаску

// ПЛОХО - перерисовка много раз
for (let i = 0; i < 1000; i++) {
  element.style.width = i + 'px';  // Каждая итерация может вызвать перерисовку
}

// ХОРОШО - все изменения в одной макротаске
setTimeout(() => {
  for (let i = 0; i < 1000; i++) {
    element.style.width = i + 'px';
  }
}, 0);

3. Используй requestAnimationFrame для анимаций

// requestAnimationFrame синхронизируется с перерисовкой браузера
function animate() {
  element.style.transform = `translateX(${x}px)`;
  x += 5;
  
  if (x < 500) {
    requestAnimationFrame(animate);
  }
}

animate();

Инструменты для отладки Event Loop

// Визуализируем порядок выполнения
function log(message) {
  const timestamp = new Date().toLocaleTimeString();
  console.log(`[${timestamp}] ${message}`);
}

log('Start');

setTimeout(() => log('setTimeout'), 0);
Promise.resolve().then(() => log('Promise'));
queueMicrotask(() => log('Microtask'));

log('End');

// Вывод покажет точный порядок

Выводы

Браузер понимает, когда брать задачу из стека на основе:

  1. Call Stack пустой? Проверить Microtask Queue
  2. Microtask Queue не пустой? Выполнить ВСЕ микротаски
  3. Microtask Queue пустой? Взять одну макротаску
  4. После макротаски снова проверить Microtask Queue

Этот порядок гарантирует, что Promise.then() всегда выполнится раньше setTimeout(), что критично для корректного поведения асинхронного кода в JavaScript.