Вивчай

Async/Await

JavaScript Async/Await — людина спокійно читає книгу в кріслі очікуючи доставку за вікномJavaScript Async/Await — людина спокійно читає книгу в кріслі очікуючи доставку за вікном

У попередньому уроці ми вивчили Promises та .then()/.catch() ланцюжки. Вони набагато кращі за callbacks, але при складних сценаріях код все ще може бути громіздким. async/await — це синтаксичний цукор поверх Promises, який робить асинхронний код схожим на звичайний синхронний.


async функції

Ключове слово async перед функцією означає дві речі:

  1. Функція завжди повертає Promise
  2. Всередині неї можна використовувати await
async function greet() {
  return "Привіт!";
}

// Еквівалент:
function greet() {
  return Promise.resolve("Привіт!");
}

// Обидва варіанти працюють однаково:
greet().then((message) => console.log(message)); // "Привіт!"

Навіть якщо функція повертає звичайне значення, async автоматично загортає його в Promise.


await — очікування результату

await зупиняє виконання async функції до тих пір, поки Promise не завершиться, і повертає його результат:

async function loadUser() {
  console.log("Починаємо завантаження...");

  // await "розгортає" Promise — повертає результат
  const user = await fetchUser(1);

  console.log("Користувач:", user.name);
  return user;
}

loadUser();
Увага

await можна використовувати тільки всередині async функції (або на верхньому рівні ES-модулів). Використання await у звичайній функції спричинить синтаксичну помилку.


Порівняння: .then() vs async/await

Ланцюжок .then()

function loadUserData(id) {
  return fetchUser(id)
    .then((user) => {
      console.log("Користувач:", user.name);
      return fetchPosts(user.id);
    })
    .then((posts) => {
      console.log("Постів:", posts.length);
      return fetchComments(posts[0].id);
    })
    .then((comments) => {
      console.log("Коментарів:", comments.length);
    })
    .catch((error) => {
      console.error("Помилка:", error.message);
    });
}

async/await

async function loadUserData(id) {
  try {
    const user = await fetchUser(id);
    console.log("Користувач:", user.name);

    const posts = await fetchPosts(user.id);
    console.log("Постів:", posts.length);

    const comments = await fetchComments(posts[0].id);
    console.log("Коментарів:", comments.length);
  } catch (error) {
    console.error("Помилка:", error.message);
  }
}
Порада

Код з async/await читається зверху вниз, як звичайний синхронний код. Кожен await — це "пауза" до отримання результату. При цьому JavaScript не блокується — він просто переключається на інші задачі.


Обробка помилок: try/catch

try/catch замінює .catch() для async/await:

async function getUser(id) {
  try {
    const user = await fetchUser(id);
    console.log("Успіх:", user.name);
    return user;
  } catch (error) {
    console.error("Помилка:", error.message);
    return null; // повертаємо значення за замовчуванням
  } finally {
    console.log("Запит завершено");
  }
}

Різні типи помилок

async function fetchData(url) {
  try {
    const response = await fetch(url);

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

    const data = await response.json();
    return data;
  } catch (error) {
    if (error.name === "TypeError") {
      console.error("Мережева помилка (немає з'єднання)");
    } else {
      console.error("Помилка:", error.message);
    }
    return null;
  }
}

Послідовне vs паралельне виконання

Послідовне (повільне)

Кожен запит чекає попереднього:

async function loadSequential() {
  const user1 = await fetchUser(1); // 1 сек
  const user2 = await fetchUser(2); // + 1 сек
  const user3 = await fetchUser(3); // + 1 сек

  // Загалом: ~3 секунди
  return [user1, user2, user3];
}

Паралельне (швидке)

Запити запускаються одночасно:

async function loadParallel() {
  const [user1, user2, user3] = await Promise.all([
    fetchUser(1), // всі три
    fetchUser(2), // запускаються
    fetchUser(3), // одночасно
  ]);

  // Загалом: ~1 секунда
  return [user1, user2, user3];
}
Увага

Це найпоширеніша помилка з async/await: використання await в циклі, коли запити не залежать один від одного. Це робить код у 3-10 разів повільнішим.

Антипатерн: await у циклі

// ❌ ПОВІЛЬНО — послідовні запити
async function loadAllUsers(ids) {
  const users = [];
  for (const id of ids) {
    const user = await fetchUser(id); // чекає кожного
    users.push(user);
  }
  return users;
}

// ✅ ШВИДКО — паралельні запити
async function loadAllUsers(ids) {
  const users = await Promise.all(
    ids.map((id) => fetchUser(id))
  );
  return users;
}

Top-level await

В ES-модулях (файлах з import/export) можна використовувати await на верхньому рівні:

// config.js (ES module)
const response = await fetch("/api/config");
const config = await response.json();

export default config;
Інфо

Top-level await працює тільки в ES-модулях (type: "module" в package.json або <script type="module">). У CommonJS (require) це не підтримується.


Конвертація .then() в async/await

Крок за кроком:

// Було: .then() ланцюжок
function getUserPosts(userId) {
  return fetch(`/api/users/${userId}`)
    .then((response) => response.json())
    .then((user) => fetch(`/api/posts?author=${user.name}`))
    .then((response) => response.json())
    .catch((error) => {
      console.error(error);
      return [];
    });
}

// Стало: async/await
async function getUserPosts(userId) {
  try {
    const userResponse = await fetch(`/api/users/${userId}`);
    const user = await userResponse.json();

    const postsResponse = await fetch(`/api/posts?author=${user.name}`);
    const posts = await postsResponse.json();

    return posts;
  } catch (error) {
    console.error(error);
    return [];
  }
}

async у різних контекстах

Arrow functions

const loadUser = async (id) => {
  const user = await fetchUser(id);
  return user;
};

Методи об'єктів

const api = {
  async getUser(id) {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
  },

  async getPosts(userId) {
    const response = await fetch(`/api/posts?userId=${userId}`);
    return response.json();
  },
};

// Використання
const user = await api.getUser(1);
const posts = await api.getPosts(user.id);

Класи

class UserService {
  async getById(id) {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) throw new Error("Користувача не знайдено");
    return response.json();
  }
}

Практика

Створи набір async функцій, що симулюють API:

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

// Симуляція API
async function getUser(id) {
  await delay(500);
  return { id, name: `Користувач ${id}`, role: "user" };
}

async function getUserPosts(userId) {
  await delay(300);
  return [
    { id: 1, title: "Перший пост", userId },
    { id: 2, title: "Другий пост", userId },
  ];
}

async function getPostComments(postId) {
  await delay(200);
  return [
    { id: 1, text: "Чудовий пост!", postId },
    { id: 2, text: "Дякую за інформацію", postId },
  ];
}

// Використання: завантаж користувача, його пости та коментарі
async function main() {
  try {
    const user = await getUser(1);
    console.log("Користувач:", user.name);

    const posts = await getUserPosts(user.id);
    console.log("Пости:", posts.map((p) => p.title));

    // Паралельно завантажуємо коментарі до всіх постів
    const allComments = await Promise.all(
      posts.map((post) => getPostComments(post.id))
    );
    console.log("Коментарі:", allComments.flat());
  } catch (error) {
    console.error("Помилка:", error.message);
  }
}

main();

Підсумок

  • async перед функцією означає: повертає Promise, дозволяє await
  • await "розгортає" Promise та повертає його результат
  • try/catch замінює .catch() для обробки помилок
  • Послідовне виконання: await один за одним (коли результат залежить від попереднього)
  • Паралельне виконання: Promise.all([...]) (коли запити незалежні)
  • Не використовуй await в циклах для незалежних запитів — це повільно

Що далі?

У наступному уроці — Fetch API, де ми нарешті почнемо робити справжні HTTP-запити до серверів.

Інфо

Корисні посилання: