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

Как может асинхронный код выполняться параллельно с синхронным?

1.0 Junior🔥 181 комментариев
#JavaScript Core#Браузер и сетевые технологии

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

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

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

Как может асинхронный код выполняться параллельно с синхронным?

Это возможно благодаря Event Loop в JavaScript. Язык работает в одном потоке, но с помощью асинхронного выполнения может создавать иллюзию параллелизма, когда асинхронный код и синхронный выполняются "одновременно".

Event Loop: основы

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

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

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

console.log('3. Конец синхронного кода');

// Вывод:
// 1. Начало
// 3. Конец синхронного кода
// 2. Асинхронный код из setTimeout

Как это работает: архитектура Event Loop

JavaScript имеет три ключевых компонента:

  1. Call Stack — стек вызовов (выполняет синхронный код)
  2. Web API — браузерные API (XMLHttpRequest, setTimeout, fetch)
  3. Event Queue — очередь событий (асинхронные операции)
┌─────────────────────────────────────────┐
│         JavaScript Engine               │
│  ┌──────────────┐                      │
│  │  Call Stack  │  (синхронный код)    │
│  └──────────────┘                      │
└─────────────────────────────────────────┘
           ↕
   ┌──────────────────┐
   │    Web APIs      │
   │  setTimeout      │
   │  fetch           │
   │  XMLHttpRequest  │
   └──────────────────┘
           ↕
   ┌──────────────────┐
   │  Event Queue     │
   │  (Callback Queue)│
   └──────────────────┘

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

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

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => {
    console.log('3. Асинхронный код (fetch завершен)');
  });

setTimeout(() => {
  console.log('4. Асинхронный код (setTimeout)');
}, 1000);

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

// Порядок выполнения:
// 1. Синхронный код 1
// 2. Синхронный код 2
// 3. Асинхронный код (fetch завершен)  — когда fetch завершится
// 4. Асинхронный код (setTimeout)     — через 1 секунду

Почему асинхронный код не блокирует синхронный?

Асинхронные операции делегируются браузеру/операционной системе:

// Синхронная операция (блокирует)
function blockingLoop() {
  for (let i = 0; i < 1000000000; i++) {
    // Долгая операция — пока она выполняется, 
    // ничего другого не может выполниться
  }
}

blockingLoop();  // UI зависнет!

// Асинхронная операция (не блокирует)
setTimeout(() => {
  for (let i = 0; i < 1000000000; i++) {
    // Эта операция делегирована асинхронному выполнению
    // UI остаётся отзывчивым
  }
}, 0);

Promise и Event Loop

Promise используют Microtask Queue, которая имеет больший приоритет, чем Event Queue:

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

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

Promise.resolve()
  .then(() => {
    console.log('2. Promise (Microtask)');
  });

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

// Вывод:
// 1. Синхронный код
// 3. Ещё синхронный код
// 2. Promise (Microtask) — выполнится раньше setTimeout!
// 4. setTimeout (Macrotask)

Очерёдность выполнения (Priority)

// Высший приоритет → Низший приоритет
// 1. Call Stack (синхронный код)
// 2. Microtask Queue (Promises, async/await)
// 3. Macrotask Queue (setTimeout, setInterval, events)

console.log('1. Синхронно');

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

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

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

// Результат:
// 1. Синхронно
// 2. Microtask (Promise)
// 3. Microtask (Promise 2)
// 4. Macrotask (setTimeout)

Реальный пример: загрузка данных

function loadUserData(userId) {
  console.log('1. Начало запроса');  // Синхронно
  
  fetch(`/api/user/${userId}`)
    .then(response => response.json())  // Microtask
    .then(data => {
      console.log('3. Данные получены:', data);  // Microtask
      updateUI(data);
    })
    .catch(error => {
      console.error('Ошибка:', error);  // Microtask
    });
  
  console.log('2. Запрос инициирован');  // Синхронно
}

loadUserData(123);
// Вывод:
// 1. Начало запроса
// 2. Запрос инициирован
// 3. Данные получены: {...}

async/await и Event Loop

async function processData() {
  console.log('1. Синхронно в начале функции');
  
  const data = await fetch('/api/data').then(r => r.json());
  // Здесь выполнение функции приостанавливается
  
  console.log('3. Асинхронно после await');
}

console.log('2. Синхронно вне функции');

processData();

// Вывод:
// 1. Синхронно в начале функции
// 2. Синхронно вне функции
// 3. Асинхронно после await

Best Practices

✅ Понимание Event Loop:

// Используй это понимание для оптимизации
// Microtask (Promises) выполнятся ДО Macrotask (setTimeout)

async function efficient() {
  const data = await fetch('/api/data').then(r => r.json());
  // Выполнится быстрее, чем setTimeout
}

✅ Избегай блокирования синхронным кодом:

// Плохо: блокирует UI
function slowSync() {
  const result = heavyComputation();  // Блокирует!
  return result;
}

// Хорошо: не блокирует
async function fastAsync() {
  const result = await heavyComputationAsync();  // Не блокирует
  return result;
}

Вывод: асинхронный код может выполняться "параллельно" с синхронным благодаря Event Loop. JavaScript обрабатывает синхронный код в Call Stack, а асинхронные операции делегирует браузеру, который возвращает результаты в очередь событий. Event Loop постоянно проверяет эту очередь и выполняет следующий callback только когда Call Stack пуст.

Как может асинхронный код выполняться параллельно с синхронным? | PrepBro