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

Как браузер определяет, что асинхронный код не выполняется?

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

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

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

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

Как браузер определяет, что асинхронный код не выполняется?

Это интересный вопрос, который касается того, как браузер отслеживает состояние выполнения асинхронного кода и управляет event loop'ом. Ответ связан с концепциями event loop, task queue и microtask queue.

Event Loop и его состояния

Browser engine постоянно работает в цикле:

while (eventLoop.waitForTask()) {
  // 1. Выполнить все синхронные скрипты (call stack)
  const macrotask = eventLoop.nextMacrotask();
  if (macrotask) {
    execute(macrotask);
  }
  
  // 2. Выполнить ВСЕ микротаски
  while (microtaskQueue.hasTasks()) {
    execute(microtaskQueue.nextMicrotask());
  }
  
  // 3. Отрендерить (если нужно)
  if (isRepaintTime()) {
    repaint();
  }
}

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

1. Пустой Call Stack

Основной признак — стек вызовов полностью пуст. Это означает, что весь синхронный код выполнен.

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

setTimeout(() => {
  console.log('3. Асинхронный код (макротаска)');
}, 0);

Promise.resolve().then(() => {
  console.log('2. Асинхронный код (микротаска)');
});

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

// Вывод:
// 1. Синхронный код
// 4. Ещё синхронный код
// -- Call stack пуст, браузер начинает выполнять очереди --
// 2. Асинхронный код (микротаска)
// 3. Асинхронный код (макротаска)

2. Пустая Task Queue (макротаски)

Макротаски (setTimeout, setInterval, fetch callbacks) хранятся в task queue. Браузер проверяет эту очередь:

// Макротаски, которые браузер отслеживает:
// - setTimeout / setInterval
// - fetch и XMLHttpRequest
// - User interactions (click, scroll, etc.)
// - Rendering tasks
// - MessageChannel

setTimeout(() => console.log('Макротаска 1'), 0);
setTimeout(() => console.log('Макротаска 2'), 0);

// Браузер видит в task queue две задачи
// После выполнения первой — ещё есть вторая
// После выполнения второй — очередь пуста

3. Пустая Microtask Queue

Микротаски (Promise callbacks, queueMicrotask) в отдельной очереди.

Promise.resolve()
  .then(() => console.log('Микротаска 1'))
  .then(() => console.log('Микротаска 2'));

queueMicrotask(() => console.log('Микротаска 3'));

// Браузер выполняет ВСЕ микротаски перед следующей макротаской

4. Нет ожидающих асинхронных операций

Браузер отслеживает все активные асинхронные операции:

// Активная операция — fetch
const promise = fetch('/api/data');

// Браузер знает, что есть ожидающая операция
// Даже если call stack пуст, браузер НЕ считает код завершённым

promise.then(response => {
  console.log('Ответ получен');
});

// После получения ответа микротаска (then) выполняется

Как это работает в коде?

const eventLoop = {
  callStack: [],
  taskQueue: [],
  microtaskQueue: [],
  pendingOperations: new Set() // fetch, setTimeout, etc.
};

// Браузер постоянно проверяет:
function isAsyncCodeRunning() {
  return (
    eventLoop.callStack.length > 0 ||           // Синхронный код
    eventLoop.taskQueue.length > 0 ||           // Макротаски
    eventLoop.microtaskQueue.length > 0 ||      // Микротаски
    eventLoop.pendingOperations.size > 0        // Операции (fetch, setTimeout)
  );
}

if (!isAsyncCodeRunning()) {
  console.log('Весь асинхронный код выполнен');
  // Браузер может:
  // - Заснуть (idle)
  // - Выполнить другие задачи ОС
  // - Слушать пользовательские события
}

Practical example

// Пример 1: Простой setTimeout
console.log('Start');
setTimeout(() => {
  console.log('Timeout finished');
}, 100);
console.log('End');

// Call stack: ['console.log', 'setTimeout']
// После обоих console.log -> call stack пуст
// setTimeout добавлен в task queue
// Браузер ждёт 100ms
// После 100ms выполняет callback

// Вывод:
// Start
// End
// Timeout finished (после паузы)
// Пример 2: Promise
console.log('Start');

fetch('/api').then(res => res.json());

console.log('End');

// Call stack: empty после двух console.log
// Браузер видит, что есть fetch в pendingOperations
// .then() добавляется в microtask queue (будет выполнена после fetch)
// Браузер ждёт ответа

// Вывод:
// Start
// End
// (depois ответа сервера)
// { ...json response }

Debug Tools

Модерн браузеры позволяют видеть это в Performance tab:

// Можно использовать Performance API
performance.mark('start');

setTimeout(() => {
  performance.mark('end');
  performance.measure('async-task', 'start', 'end');
}, 100);

// В DevTools -> Performance tab видно timeline выполнения

Что мешает коду завершиться?

// 1. Бесконечный setInterval
const intervalId = setInterval(() => {
  console.log('Будет выполняться вечно');
}, 1000);
// Код НИКОГДА не завершится, пока не вызовешь:
clearInterval(intervalId);

// 2. Зависший Promise
const promise = new Promise(resolve => {
  // resolve НИКОГДА не вызывается
  // Promise остаётся в pending состоянии ВЕЧНО
});

// 3. Бесконечная цепочка микротасок
function infiniteMicrotasks() {
  Promise.resolve().then(() => infiniteMicrotasks());
}
infiniteMicrotasks();
// Браузер будет делать микротаски ВЕЧНО, никогда не выполнит макротаски

Практический мониторинг

// Можно создать функцию для отладки
function isEventLoopBusy() {
  const start = performance.now();
  
  return new Promise(resolve => {
    setTimeout(() => {
      const end = performance.now();
      const delay = end - start;
      resolve({
        isBusy: delay > 10, // Если setTimeout выполнен с задержкой > 10ms
        actualDelay: delay
      });
    }, 0);
  });
}

// Использование
const result = await isEventLoopBusy();
if (result.isBusy) {
  console.log('Event loop занят!');
}

Выводы

Браузер определяет, что асинхронный код НЕ выполняется, когда:

  1. Call stack пуст — нет синхронного кода в стеке вызовов
  2. Task queue пуста — нет макротасок (setTimeout, fetch callbacks)
  3. Microtask queue пуста — нет микротасок (Promise, queueMicrotask)
  4. Нет pending операций — все fetch, XMLHttpRequest завершены или отменены
  5. Браузер перейдёт в режим ожидания (idle state)

Этот механизм критичен для:

  • Оптимизации производительности
  • Отладки зависаний приложения
  • Управления нагрузкой на CPU
  • Обработки пользовательских событий

Основной вывод: браузер отслеживает все очереди и операции в реальном времени, позволяя эффективно управлять асинхронным кодом.