Асинхронность. Промисы и async/await

Free Preview
Продолжительность: 15 мин

Когда мы пишем обычный код, он выполняется строка за строкой — синхронно. Но что если нужно загрузить данные с сервера или подождать ответа пользователя? Такие операции занимают неизвестное время, и если просто «подождать» — браузер зависнет и перестанет реагировать.

Для решения этой задачи в JavaScript существует асинхронность.

Однопоточность JavaScript

JavaScript — однопоточный язык. Это означает, что он может выполнять только одну операцию в один момент времени. Пока выполняется одна задача, все остальные ждут.

Браузер решает эту проблему с помощью цикла событий (event loop): медленные операции (запросы к серверу, таймеры, ввод пользователя) отдаются в отдельную очередь, а основной поток продолжает работу. Когда операция завершается, её результат возвращается обратно.

Детально механика event loop — тема для отдельного глубокого изучения. Отличное объяснение с визуализацией можно найти в видео «What the heck is the event loop anyway?» (на английском).

Колбэки — первый подход

Исторически первым решением были колбэки — функции, которые передаются как аргументы и вызываются, когда операция завершилась. Мы уже встречали их: setTimeout, forEach, обработчики событий — всё это колбэки.

setTimeout(() => {
  console.log('Прошла секунда');
}, 1000);

console.log('Это выполнится сразу');
// 'Это выполнится сразу'
// 'Прошла секунда' — через секунду
js

Проблема возникает, когда операции нужно выполнять последовательно. Код превращается в так называемый callback hell — глубокую вложенность колбэков, которую сложно читать и поддерживать:

loadUser(id, (user) => {
  loadProfile(user, (profile) => {
    loadPosts(profile, (posts) => {
      // ещё глубже...
    });
  });
});
js

Promise — обещание

Promise (обещание) — это объект, представляющий результат асинхронной операции, который станет известен в будущем. Promise может находиться в одном из трёх состояний:

  • pending — ожидает выполнения;
  • fulfilled — выполнен успешно;
  • rejected — завершился с ошибкой.

Цепочка .then() / .catch() позволяет обработать результат:

fetch('https://api.example.com/cities')
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.log('Ошибка:', error);
  });
js

Каждый .then() получает результат предыдущего и может вернуть новое значение или новый Promise. Это позволяет выстраивать читаемые цепочки вместо вложенных колбэков.

async / await — современный синтаксис

async/await — это надстройка над Promise, которая позволяет писать асинхронный код так, будто он синхронный:

async function loadCities() {
  try {
    const response = await fetch('https://api.example.com/cities');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.log('Ошибка:', error);
  }
}

loadCities();
js

Ключевое слово async перед функцией означает, что она всегда возвращает Promise. Ключевое слово await внутри такой функции «приостанавливает» выполнение и ждёт, пока Promise выполнится — при этом браузер не зависает, остальные задачи продолжают работать.

Блок try/catch здесь заменяет .catch() и ловит ошибки, как в синхронном коде.

fetch — запросы к серверу

fetch — это встроенная в браузер функция для отправки HTTP-запросов. Она возвращает Promise:

async function getUser(id) {
  const response = await fetch(`https://api.example.com/users/${id}`);

  if (!response.ok) {
    throw new Error(`Ошибка: ${response.status}`);
  }

  const user = await response.json();
  return user;
}
js

fetch — основной инструмент для работы с серверными данными в браузере. Он поддерживает все типы запросов (GET, POST, PUT, DELETE) и настройку заголовков. Подробнее можно почитать на doka.guide.

Итого

Асинхронность — важная и обширная тема, которую невозможно полностью охватить в рамках обзорного урока. Главное, что нужно запомнить:

  • JavaScript однопоточный, но умеет работать с медленными операциями не блокируя браузер;
  • Колбэки — первый подход, но при вложенности становятся нечитаемыми;
  • Promise — удобнее, цепочки .then()/.catch();
  • async/await — самый современный и читаемый способ работы с асинхронным кодом;
  • fetch — стандартный инструмент для HTTP-запросов.

Для более глубокого изучения темы рекомендую статью об асинхронности на doka.guide и раздел про Promise на MDN.

Это завершающий урок третьего модуля. В следующем модуле мы применим всё изученное на практике — научимся работать с DOM-деревом и добавлять интерактивность на нашу страницу.