Async/Await
JavaScript Async/Await — людина спокійно читає книгу в кріслі очікуючи доставку за вікном
У попередньому уроці ми вивчили Promises та .then()/.catch() ланцюжки. Вони набагато кращі за callbacks, але при складних сценаріях код все ще може бути громіздким. async/await — це синтаксичний цукор поверх Promises, який робить асинхронний код схожим на звичайний синхронний.
async функції
Ключове слово async перед функцією означає дві речі:
- Функція завжди повертає Promise
- Всередині неї можна використовувати
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, дозволяєawaitawait"розгортає" Promise та повертає його результатtry/catchзамінює.catch()для обробки помилок- Послідовне виконання:
awaitодин за одним (коли результат залежить від попереднього) - Паралельне виконання:
Promise.all([...])(коли запити незалежні) - Не використовуй
awaitв циклах для незалежних запитів — це повільно
Що далі?
У наступному уроці — Fetch API, де ми нарешті почнемо робити справжні HTTP-запити до серверів.
Корисні посилання:
- javascript.info: Async/await — повний підручник (українською)
- MDN: async function — документація
- Fireship: Async/Await — коротке відео-пояснення (англійською)