$ sudo teach IT
МОДУЛЬ 10 · УРОК 4

async/await и обработка ошибок

async/await — это красивый синтаксис поверх промисов. С ним асинхронный код выглядит как синхронный: пишешь сверху вниз, читаешь как обычную историю. Это то, как пишут 99% современного JavaScript-кода.

⏱ ~25 минут 🎓 Средний ⚡ Асинхронность

🔷 async — функция всегда возвращает Promise

Ключевое слово async перед функцией делает её асинхронной. Главное последствие: такая функция всегда возвращает Promise, даже если внутри нет ничего асинхронного. Если функция вернёт обычное значение — оно автоматически обернётся в успешный промис.

// Обычная async-функция
async function greet() {
  return 'Привет!'; // автоматически → Promise.resolve('Привет!')
}

// Вызываем как обычно — но результат это промис
greet().then(msg => console.log(msg)); // 'Привет!'

// Стрелочная async-функция
const getData = async () => {
  return { users: [] };
};

// Проверим:
console.log(getData() instanceof Promise); // true!

⏸️ await — ждём промис

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

// Вспомогательная функция — имитирует задержку
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function loadData() {
  console.log('Начинаем загрузку...');

  await delay(2000); // ждём 2 секунды

  console.log('Данные загружены!');

  return { name: 'Иван', age: 25 };
}

loadData().then(data => console.log(data));
// Начинаем загрузку...
// (через 2 сек) Данные загружены!
// { name: 'Иван', age: 25 }

💡 Магия await: код после await выглядит синхронным, но на самом деле асинхронный. Функция ставится на паузу, браузер обрабатывает другие события, а когда промис выполнится — функция продолжается с того места.

🛡️ Обработка ошибок: try/catch

В async/await обработка ошибок выглядит как в обычном синхронном коде — через try/catch. Если промис отклоняется — ошибка «бросается» и попадает в блок catch.

async function fetchUser(id) {
  try {
    const response = await fetch(`/api/users/${id}`);

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

    const data = await response.json();
    return data;

  } catch (error) {
    console.error('Не удалось загрузить пользователя:', error.message);
    return null; // возвращаем null при ошибке

  } finally {
    console.log('Запрос завершён');
    // скрываем лоадер и т.д.
  }
}

// Использование
const user = await fetchUser(42);
if (user) {
  console.log(user.name);
}

⚠️ Не забывай про try/catch! Необработанная ошибка в async-функции создаёт «unhandled promise rejection» — предупреждение в консоли и потенциально сломанное состояние приложения.

🚀 Параллельное выполнение через Promise.all

Если написать несколько await подряд — они выполнятся последовательно. Но иногда нужно запустить несколько запросов одновременно и дождаться всех. Для этого используют Promise.all вместе с await:

// ❌ Плохо: 3 секунды (1+1+1)
async function sequential() {
  const users = await fetchUsers();     // 1 сек
  const products = await fetchProducts(); // + 1 сек
  const orders = await fetchOrders();   // + 1 сек
  return { users, products, orders };
}

// ✅ Хорошо: ~1 секунда (параллельно)
async function parallel() {
  const [users, products, orders] = await Promise.all([
    fetchUsers(),
    fetchProducts(),
    fetchOrders()
  ]);
  return { users, products, orders };
}

📊 async/await vs .then() — что выбрать?

Через .then() цепочку:
fetch('/api/data')
  .then(res => res.json())
  .then(data => {
    console.log(data);
  })
  .catch(err => {
    console.error(err);
  });
Через async/await:
async function loadData() {
  try {
    const res = await fetch('/api/data');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

Оба варианта делают одно и то же. async/await — предпочтителен для большинства случаев: код читается линейно, легче добавить промежуточные операции, удобнее дебажить. .then() удобен для коротких цепочек и потоков данных.

✅ Итоги урока

  • async перед функцией — она всегда возвращает Promise
  • await — ждёт промис, используется только внутри async-функции
  • try/catch/finally — стандартная обработка ошибок в async/await
  • Несколько await подряд — последовательное выполнение
  • await Promise.all([...]) — параллельное выполнение нескольких промисов
  • async/await — это синтаксический сахар над промисами, не замена им
  • Предпочитай async/await за читаемость; .then() удобен для коротких цепочек

В следующем уроке разберём JSON — универсальный формат обмена данными между фронтом и бэкендом. 🚀

async/await и обработка ошибок

7 вопросов

async функция с try/catch

Напишите async функцию fetchUserData(id), которая имитирует запрос к серверу. Используйте функцию delay(ms) (реализуйте её), затем проверьте: если id меньше 1 — выбросьте ошибку через throw. В случае ошибки верните null. Используйте try/catch.

Параллельные запросы через Promise.all

Реализуйте async функцию loadAllUsers(ids), которая принимает массив ID пользователей и загружает их параллельно. Для каждого ID используйте функцию fetchUserData(id) из предыдущего задания (просто имитируйте её через delay + return). Используйте Promise.all с map. Верните массив результатов.