async / await.fetch().try...catch.Більшість сучасних веб-додатків не зберігають усі дані (статті, інформацію про користувачів, товари) безпосередньо в HTML-файлі. Це було б неефективно і небезпечно. Натомість, веб-сторінка (Клієнт) постійно “спілкується” з віддаленим комп’ютером (Сервером), щоб завантажити свіжі дані або відправити нові (наприклад, опублікувати коментар).
Це спілкування відбувається у фоновому режимі (без перезавантаження всієї сторінки браузера) за допомогою мережевих запитів. Історично для цього використовувалася технологія AJAX (XMLHttpRequest), але сьогодні стандартом став сучасний, набагато зручніший механізм — Fetch API.
Оскільки будь-який запит до сервера через Інтернет займає непередбачуваний час (від мілісекунд до десятків секунд у разі поганого зв’язку), програма не може просто завмерти і чекати на відповідь. JS повинен вміти обробляти ці задачі асинхронно.
Основою асинхронності в сучасному JavaScript є Promises (Обіцянки) та синтаксис async/await.
У вебі є дві головні дійові особи:
Щоб Клієнт і Сервер розуміли один одного, вони використовують спеціальний договір-інтерфейс, який називається API.
Найпопулярніший формат API сьогодні — REST API. Він базується на використанні стандартних HTTP-методів для різних операцій (називається CRUD: Create, Read, Update, Delete):
Сервер може бути написаний на іншій мові програмування (Python, Java, PHP), а браузер розуміє лише JavaScript. Тому їм потрібна спільна мова для обміну даними. Сьогодні цією “мова-есперанто” є JSON.
Це просто довгий текстовий рядок (String), який візуально дуже нагадує звичайний об’єкт JavaScript, але з жорсткими правилами:
"".Приклад JSON, отриманого від сервера:
{
"user_id": 145,
"name": "Ivan",
"is_active": true,
"hobbies": ["reading", "cycling"]
}
У JS є два вбудовані методи для миттєвої конвертації:
JSON.parse(string) — перетворює JSON-текст у реальний JS О’бєкт.JSON.stringify(object) — перетворює JS Об’єкт у JSON-текст (для відправки на сервер).Коли ми просимо браузер завантажити дані (наприклад, велике фото), ми не знаємо, коли це закінчиться. Раніше єдиним способом відреагувати на завантаження були функції зворотного виклику (Callbacks):
// Старий, поганий код!
downloadData(
"https://api.site.com/users",
function (users) {
downloadUserAvatar(
users[0],
function (avatar) {
displayAvatar(
avatar,
function () {
console.log("Ура, все готово");
},
function (error) {
/* Обробка помилки відображення */
},
);
},
function (error) {
/* Обробка помилки аватарки */
},
);
},
function (error) {
/* Обробка помилки списку */
},
);
Коли одна асинхронна операція залежить від результату попередньої, код перетворюється на нечитабельну “піраміду смерті” (Callback Hell).
Щоб врятувати розробників від “Піраміди смерті”, в ES6 був доданий Проміс (Promise).
Promise — це спеціальний об’єкт у JS, який представляє результат асинхронної операції, який БУДЕ доступний у майбутньому (вона обіцяє нам повернути дані).
Проміс незримо існує в одному з трьох станів:
Спочатку для споживання промісів використовували методи .then() (якщо добре) та .catch() (якщо помилка).
// Класичне споживання Promise
fetch("https://api.test/data")
// .then повертає новий Promise, дозволяючи будувати ланцюжки
.then((response) => response.json())
.then((data) => console.log("Дані отримано: ", data))
.catch((error) => console.error("Щось пішло не так: ", error));
async / awaitХоча .then() вирішив багато проблем, ланцюжки все одно були важкими для читання.
З виходом ES8 розробники отримали “синтаксичний цукор” — ключові слова async та await. Це найсучасніший, найчистіший спосіб роботи з Промісами.
Він дозволяє писати асинхронний код так, ніби він є звичайним синхронним (рядок за рядком).
Правила async/await:
await лише всередині функції (або методу), перед якою стоїть слово async.await встановлюється ПЕРЕД промісом. Воно змушує JS-код всередині цієї функції “стати на паузу” і дочекатися, поки Promise перейде у стан Fulfilled. Після цього воно поверне реальні дані (без всяких then)./* Сучасний, красивий код: */
async function loadUserData() {
console.log("Починаємо...");
// JS робить паузу (всередині функції) і чекає на відповідь сервера
const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
// JS розуміє: ага, response вже тут
console.log("Відповідь є!");
// Нам треба почекати парсинг JSON (він теж асинхронний!)
const user = await response.json();
console.log(`Привіт, ${user.name}!`);
}
loadUserData();
fetch()Функція fetch(url, [options]) вбудована у всі сучасні браузери. Її призначення — робити HTTP-запити. Вона “під капотом” завжди повертає Promise.
Якщо ви викликаєте fetch() з одним аргументом (тільки URL) — він завжди створює стандартний GET-запит (тобто просить віддати йому інформацію).
Особливість: fetch не вважає помилки з кодом 404 (Не знайдено) або 500 (Ой, сервер зламався) серйозними (Rejected). Для fetch відхилений проміс — це лише ситуація, коли взагалі немає інтернету і він не зміг “достукатися” до сервера.
Тому розробники завжди повинні вручну перевіряти властивість response.ok. Повертає true (якщо код 200..299).
Щоб не тільки прочитати, але й зберегти нові дані (наприклад, форму реєстрації), ми маємо передати у fetch другий аргумент — об’єкт з налаштуваннями (options):
async function createPost(postTitle, postBody) {
const newPost = {
title: postTitle,
body: postBody,
userId: 1,
};
// Об'єкт налаштувань
const options = {
method: "POST", // Вказуємо, що ми створюємо, а не читаємо
headers: {
// Критично важливо сказати серверу, який саме формат ми зараз надішлемо
"Content-Type": "application/json",
},
// Перетворюємо реальний об'єкт JS у текстовий JSON-код!
body: JSON.stringify(newPost),
};
// Відправляємо
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts",
options,
);
if (response.ok) {
const savedData = await response.json();
console.log("Успішно створено з ID:", savedData.id);
}
}
try...catchОскільки при використанні async/await ми відмовилися від блоку .catch(), виникає питання: “А що буде, якщо інтернет зникне на середині завантаження?”.
Сценарій видасть неконтрольовану помилку (Uncaught Promise Rejection) і додаток може зламатися.
Щоб цьому запобігти, ВЕСЬ асинхронний код всередині async-функції потрібно огортати в класичний блок try...catch.
async function getWeather(city) {
try {
// Пробуємо виконати небезпечний блок...
const response = await fetch(`https://weather-api.com/${city}`);
// Вручну кидаємо помилку, якщо щось не так на стороні їхнього сервера
if (!response.ok) {
throw new Error(`Хмарно з проясненнями багів: статус ${response.status}`);
}
const data = await response.json();
return data.temperature;
} catch (error) {
// Якщо ВСЮДИ вище сталася технічна або мануальна помилка — код перескакує сюди!
console.error("Сталася катастрофа:", error.message);
// Можна показати гарне повідомлення-помилку користувачу (Тостер / Модалка)
// ui.showError(error.message);
}
}
JSON.stringify та JSON.parse (або response.json())..then(), — це вказати перед викликом слово await (вкладеним у функцію з тегом async).fetch — головний інструмент веб-розробника. Без налаштувань вона використовує метод GET, але через додатковий об’єкт options її перетворюють на агрегатор для POST, PUT, DELETE.try...catch гарантує обробку помилок і захищає додаток від повного “падіння”.GET від методу POST у концепції REST API? Який з них викликається функцією fetch() за замовчуванням?await, чому без нього функція loadData() видала б помилку, або просто повернула Promise Pending? Де саме його потрібно ставити?options) при здійсненні POST-запиту ми зобов’язані передати параметр headers Content-Type: application/json?fetch може працювати без генерації системної помилки ігноруючи навіть статус “404 Not Found”? Як з цим боротися?try...catch при написанні функції з мережевими запитами? Що конкретно виконує секція catch(error)?