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

За счет чего достигается асинхронность?

2.0 Middle🔥 163 комментариев
#Soft Skills и рабочие процессы

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

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

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

Асинхронность в JavaScript - как это работает

Краткий ответ

Асинхронность в JavaScript достигается за счет Event Loop (цикл событий) и Web APIs (браузерные API типа setTimeout, fetch, XHR). JavaScript остается однопоточным, но браузер предоставляет инструменты для неблокирующего выполнения операций.

Основное понимание

console.log('1'); // Синхронно - выполняется сразу

setTimeout(() => {
  console.log('2'); // Асинхронно - выполняется позже
}, 0);

console.log('3'); // Синхронно - выполняется сразу

// Вывод:
// 1
// 3
// 2

// Почему 2 после 3? Потому что setTimeout асинхронен!

JavaScript Single Threaded (однопоточный)

Джаваскрипт может выполнять только одну операцию одновременно:

// JavaScript НЕ может делать это одновременно:
function blockingOperation() {
  let sum = 0;
  for (let i = 0; i < 1000000000; i++) {
    sum += i; // Блокирует все остальное
  }
  return sum;
}

blockingOperation(); // 30 секунд ждем, UI замерзает, события не обрабатываются

Это проблема - если операция долгая, UI замерзает. Решение - асинхронность.

Event Loop (цикл событий) - сердце асинхронности

Event Loop - это механизм, который:

  1. Выполняет синхронный код (Call Stack)
  2. Когда стек пустой, проверяет очереди (Callback Queue, Microtask Queue)
  3. Берет первый callback и выполняет его
  4. Повторяет
// Визуализация:

Call Stack (основной поток)    │    Web APIs (браузер)    │    Callback Queue
────────────────────────────    │    ─────────────────────  │    ───────────────
                                │                          │
function test() { ... }         │                          │
  |
  v
setTimeout(callback, 1000)      │  -> setTimeout (ждет)    │
  |
  v
console.log('done')             │                          │
  |
  v
(Call Stack пустой)             │    (Прошла 1 сек)        │  -> callback
                                │    (готов к выполнению)  │
                                │                          │
вызвать из Queue                │                          │  (выполнено)

Как работает setTimeout

console.log('start');

setTimeout(() => {
  console.log('timeout');
}, 1000);

console.log('end');

// Процесс:
// 1. console.log('start') -> Call Stack -> выполнить сразу
// 2. setTimeout(...) -> передать в Web API -> ждать 1 сек
// 3. console.log('end') -> Call Stack -> выполнить сразу
// 4. 1 сек прошла -> callback в Callback Queue
// 5. Call Stack пустой -> перемещаем callback из Queue -> выполняем

// Вывод:
// start
// end
// timeout

Web APIs (браузерные API)

Браузер предоставляет асинхронные API:

// 1. setTimeout / setInterval - отложенное выполнение
setTimeout(() => {
  console.log('after 1 second');
}, 1000);

// 2. fetch - HTTP запросы (асинхронно)
fetch('/api/users')
  .then(response => response.json())
  .then(data => console.log(data));

// 3. XMLHttpRequest - старый способ HTTP запросов
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', () => {
  console.log(xhr.responseText);
});
xhr.open('GET', '/api/users');
xhr.send();

// 4. DOM events - события клика, скролла и т.д.
document.addEventListener('click', () => {
  console.log('Clicked');
});

// 5. RequestAnimationFrame - синхронизация с монитором
requestAnimationFrame(() => {
  // Выполнится перед следующей перерисовкой (60fps)
  element.style.opacity = 0.5;
});

// 6. readFile (Node.js) - чтение файлов
fs.readFile('file.txt', 'utf-8', (err, data) => {
  if (!err) console.log(data);
});

Callback Queue vs Microtask Queue

Есть ДВЕ очереди, и Microtask Queue имеет выше приоритет:

// Microtask Queue (приоритет ВЫШЕ):
console.log('start');

// Promise (microtask)
Promise.resolve()
  .then(() => console.log('promise 1'))
  .then(() => console.log('promise 2'));

// setTimeout (callback queue, приоритет НИЖЕ)
setTimeout(() => {
  console.log('timeout 1');
  
  // Promise внутри setTimeout (microtask)
  Promise.resolve().then(() => console.log('promise 3'));
}, 0);

console.log('end');

// Вывод:
// start
// end
// promise 1        <- Microtask выполняется первым
// promise 2        <- Все Microtask выполняются
// timeout 1        <- Потом Callback
// promise 3        <- Потом снова Microtask

// Почему такой порядок?
// 1. Выполнить все синхронный код (start, end)
// 2. Выполнить все microtask'и (promise 1, 2)
// 3. Взять первый callback из Queue (timeout 1)
// 4. Если после callback есть новые microtask'и - выполнить их (promise 3)
// 5. Повторить

Promises (Обещания) - более удобная асинхронность

// Вместо callback'ов:
fetch('/api/users')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));

// Promise внутри:
const promise = new Promise((resolve, reject) => {
  // Это выполняется синхронно
  if (success) {
    resolve('результат'); // Отправить результат
  } else {
    reject('ошибка');     // Отправить ошибку
  }
});

promise
  .then(result => console.log(result))  // После resolve
  .catch(error => console.log(error));  // После reject

Async/Await - самый удобный способ

// Внизу все еще асинхронность, но выглядит синхронно:

async function loadUsers() {
  try {
    // Ждем (асинхронно), но выглядит синхронно
    const response = await fetch('/api/users');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

// await = передать управление браузеру, потом вернуться

Примеры асинхронных операций

Пример 1: Загрузка данных

async function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Асинхронно загружаем данные
    (async () => {
      setLoading(true);
      try {
        // fetch - асинхронная операция
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        setUser(data);
      } catch (error) {
        console.error(error);
      } finally {
        setLoading(false);
      }
    })();
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  return <div>{user?.name}</div>;
}

// За счет асинхронности:
// - fetch не блокирует UI
// - setUser вызывается позже, когда данные готовы
// - Компонент может отрендериться до получения данных

Пример 2: Event Handler

button.addEventListener('click', async (event) => {
  // Асинхронно обработать клик
  const response = await fetch('/api/action', { method: 'POST' });
  const result = await response.json();
  console.log(result);
});

// За счет асинхронности:
// - click обработается сразу
// - fetch произойдет в фоне
// - UI не замерзнет

Пример 3: Таймер

const timer = setInterval(async () => {
  // Каждые 2 сек асинхронно проверить данные
  const response = await fetch('/api/status');
  const status = await response.json();
  console.log(status);
}, 2000);

// За счет асинхронности:
// - Интервал продолжает работать
// - fetch не блокирует следующий интервал

Diagram: как работает асинхронность

Код JavaScript:
┌──────────────────────────────────────────┐
│ console.log('1');           // синхро    │
│ fetch('/api').then(...)     // асинхро   │
│ console.log('2');           // синхро    │
└──────────────────────────────────────────┘
       |
       v
┌──────────────────────────────────────────┐
│         Call Stack (основной поток)      │
│ ┌──────────────────────────────────────┐ │
│ │ 1. console.log('1')  -> выполнить    │ │
│ │ 2. fetch(...) -> отправить в API     │ │ ──┐
│ │ 3. console.log('2')  -> выполнить    │ │   │
│ │ 4. (пусто)                           │ │   │
│ └──────────────────────────────────────┘ │   │
└──────────────────────────────────────────┘   │
                                               │
       ┌─────────────────────────────────────┘
       │
       v
┌──────────────────────────────────────────┐
│      Web API (асинхронные операции)     │
│ ┌──────────────────────────────────────┐ │
│ │ fetch (ждет ответ от сервера)        │ │
│ │ setTimeout (ждет время)               │ │
│ │ DOM events (ждет клика)              │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────┘
       │
       │ (когда операция готова)
       v
┌──────────────────────────────────────────┐
│        Callback Queue / Microtask        │
│ ┌──────────────────────────────────────┐ │
│ │ then(...) callback ждет выполнения   │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────┘
       │
       │ (когда Call Stack пустой)
       v
┌──────────────────────────────────────────┐
│    Event Loop перемещает callback        │
│         обратно в Call Stack             │
└──────────────────────────────────────────┘

Важные моменты

1. JavaScript все еще однопоточен

// Никогда не будет одновременно:
let x = 0;
Promise.resolve().then(() => x++);  // Microtask
fetch('/api').then(() => x++);       // Callback
console.log(x);                      // 0 в любом случае
// x будет 1, потом 2, но никогда одновременно

2. Асинхронность не = быстрее

// Это медленнее:
const result = await fetch('/api');     // 1 сек
const result2 = await fetch('/api2');   // + 1 сек = 2 сек

// Это быстрее:
const [result, result2] = await Promise.all([
  fetch('/api'),    // 1 сек параллельно
  fetch('/api2')    // 1 сек параллельно
]);                 // = 1 сек всего

3. Error handling

// Promise
fetch('/api')
  .then(r => r.json())
  .catch(error => console.error(error));

// Async/await
try {
  const response = await fetch('/api');
  const data = await response.json();
} catch (error) {
  console.error(error);
}

Заключение

Асинхронность достигается за счет:

  1. Event Loop - браузер проверяет очереди и выполняет callback'и
  2. Web APIs - браузер предоставляет асинхронные инструменты (setTimeout, fetch, события)
  3. Promises/Async-Await - удобные синтаксис для работы с асинхронностью
  4. Callback Queue и Microtask Queue - две очереди для управления порядком выполнения

Важно помнить:

  • JavaScript остается однопоточным
  • Асинхронность не решает проблему блокирующего кода (нужно использовать Web Workers)
  • Асинхронность позволяет не блокировать UI при выполнении длительных операций
  • Порядок выполнения определяется Event Loop, а не порядком кода
За счет чего достигается асинхронность? | PrepBro