Вивчай

Fetch API

JavaScript Fetch API — поштове відділення з полицями для сортування листів та посилокJavaScript Fetch API — поштове відділення з полицями для сортування листів та посилок

У попередніх двох уроках ми вивчили Promises та async/await. Тепер застосуємо ці знання на практиці — навчимося робити HTTP-запити до серверів. fetch() — це вбудований API браузера для мережевих запитів, який повертає Promise.


Що таке API?

API (Application Programming Interface) — це набір правил, за якими програми спілкуються між собою. Коли ти відкриваєш сайт, браузер робить запит до сервера і отримує відповідь — це і є робота з API.

REST API — найпоширеніший тип API для вебзастосунків. Він використовує стандартні HTTP-методи:

МетодДіяПриклад
GETОтримати даніСписок користувачів
POSTСтворити нові даніРеєстрація нового користувача
PUT/PATCHОновити даніЗміна імені користувача
DELETEВидалити даніВидалення акаунту

Формат JSON

Сервери зазвичай повертають дані у форматі JSON (JavaScript Object Notation). Він виглядає як JavaScript об'єкт, але є текстовим рядком:

// JSON — це текст
const jsonString = '{"name": "Олексій", "age": 25, "isStudent": true}';

// Перетворення JSON → об'єкт
const user = JSON.parse(jsonString);
console.log(user.name); // "Олексій"

// Перетворення об'єкт → JSON
const backToJson = JSON.stringify(user);
console.log(backToJson); // '{"name":"Олексій","age":25,"isStudent":true}'

fetch() — основи

fetch(url) робить HTTP-запит і повертає Promise:

fetch("https://jsonplaceholder.typicode.com/users/1")
  .then((response) => response.json()) // парсимо JSON
  .then((user) => console.log(user))
  .catch((error) => console.error("Помилка:", error));

З async/await (рекомендований спосіб):

async function getUser() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  const user = await response.json();
  console.log(user);
}
Порада

fetch() повертає не дані, а об'єкт Response. Щоб отримати дані, потрібно викликати .json() (для JSON) або .text() (для тексту). Ці методи теж повертають Promise, тому потрібен ще один await.


Об'єкт Response

fetch() повертає об'єкт Response з корисними властивостями:

async function checkResponse() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users");

  console.log(response.ok);       // true (статус 200-299)
  console.log(response.status);   // 200
  console.log(response.statusText); // "OK"
  console.log(response.headers);  // Headers об'єкт

  const data = await response.json(); // тіло відповіді
  console.log(data);
}
ВластивістьОпис
response.oktrue якщо статус 200-299
response.statusHTTP статус код (200, 404, 500...)
response.json()Парсить тіло як JSON (повертає Promise)
response.text()Повертає тіло як текст (Promise)

GET-запити

GET — найпростіший запит. Він використовується для отримання даних:

// Отримати список всіх користувачів
async function getUsers() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/users");

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

    const users = await response.json();
    console.log(`Отримано ${users.length} користувачів`);

    users.forEach((user) => {
      console.log(`- ${user.name} (${user.email})`);
    });
  } catch (error) {
    console.error("Не вдалося завантажити:", error.message);
  }
}

getUsers();

GET з параметрами (query string)

// Отримати пости конкретного користувача
async function getUserPosts(userId) {
  const url = `https://jsonplaceholder.typicode.com/posts?userId=${userId}`;
  const response = await fetch(url);
  const posts = await response.json();
  return posts;
}

// Або через URLSearchParams
async function searchPosts(params) {
  const query = new URLSearchParams(params).toString();
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts?${query}`
  );
  return response.json();
}

// Використання
const posts = await searchPosts({ userId: 1, _limit: 5 });

POST-запити

POST використовується для створення нових даних. Потрібно вказати метод, заголовки та тіло:

async function createPost(post) {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(post),
    });

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

    const newPost = await response.json();
    console.log("Створено пост:", newPost);
    return newPost;
  } catch (error) {
    console.error("Не вдалося створити:", error.message);
  }
}

// Використання
createPost({
  title: "Мій перший пост",
  body: "Це тестовий пост з JavaScript!",
  userId: 1,
});
Увага

Не забувай Content-Type: application/json у заголовках при відправці JSON! Без цього сервер може не розпізнати формат даних. Також обов'язково використовуй JSON.stringify() для тіла запиту.


PUT та PATCH

PUT — повна заміна ресурсу, PATCH — часткове оновлення:

// PUT — замінює весь ресурс
async function updatePost(id, updatedPost) {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${id}`,
    {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(updatedPost),
    }
  );
  return response.json();
}

// PATCH — оновлює окремі поля
async function patchPost(id, changes) {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${id}`,
    {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(changes),
    }
  );
  return response.json();
}

// Використання
await updatePost(1, { title: "Новий заголовок", body: "Новий текст", userId: 1 });
await patchPost(1, { title: "Оновлений заголовок" });

DELETE-запити

async function deletePost(id) {
  try {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/posts/${id}`,
      { method: "DELETE" }
    );

    if (response.ok) {
      console.log(`Пост ${id} видалено`);
    }
  } catch (error) {
    console.error("Помилка видалення:", error.message);
  }
}

await deletePost(1);

Обробка помилок

fetch() має важливу особливість: він не кидає помилку при HTTP-помилках (404, 500). Помилка виникає лише при мережевих проблемах:

// ❌ fetch не кидає помилку при 404!
try {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/999");
  const data = await response.json(); // спроба парсити порожню відповідь
  console.log(data);                   // {} — порожній об'єкт
} catch (error) {
  // Цей catch НЕ спрацює при 404
}

// ✅ Правильна обробка помилок
async function safeFetch(url) {
  try {
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    if (error.name === "TypeError") {
      // Мережева помилка (немає інтернету, сервер недоступний)
      console.error("Мережева помилка:", error.message);
    } else {
      // HTTP помилка або помилка парсингу
      console.error("Помилка запиту:", error.message);
    }
    return null;
  }
}
Увага

Завжди перевіряй response.ok перед обробкою даних! fetch() вважає запит успішним навіть при статусах 404 або 500. Помилку кидає лише при проблемах з мережею.


JSONPlaceholder — безкоштовне API для практики

JSONPlaceholder — це безкоштовне fake REST API для тестування. Доступні ресурси:

РесурсURL
Користувачі/users
Пости/posts
Коментарі/comments
Альбоми/albums
Фото/photos
Todos/todos
const BASE_URL = "https://jsonplaceholder.typicode.com";

async function demo() {
  // Список користувачів
  const users = await safeFetch(`${BASE_URL}/users`);
  console.log("Користувачі:", users?.length);

  // Пости першого користувача
  const posts = await safeFetch(`${BASE_URL}/posts?userId=1`);
  console.log("Пости:", posts?.length);

  // Конкретний пост
  const post = await safeFetch(`${BASE_URL}/posts/1`);
  console.log("Пост:", post?.title);
}

demo();

Практика: відображення даних на сторінці

Ось повний приклад — завантаження користувачів та відображення у DOM:

async function displayUsers() {
  const container = document.getElementById("users");
  container.innerHTML = "<p>Завантаження...</p>";

  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/users");

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

    const users = await response.json();

    container.innerHTML = users
      .map(
        (user) => `
        <div class="user-card">
          <h3>${user.name}</h3>
          <p>Email: ${user.email}</p>
          <p>Місто: ${user.address.city}</p>
        </div>
      `
      )
      .join("");
  } catch (error) {
    container.innerHTML = `<p class="error">Помилка: ${error.message}</p>`;
  }
}

displayUsers();

CORS — коротко

Коли ти робиш запит з одного домену на інший (наприклад, з localhost на api.example.com), браузер перевіряє заголовки CORS (Cross-Origin Resource Sharing). Якщо сервер не дозволяє запити з твого домену — отримаєш помилку.

Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000'
has been blocked by CORS policy
Інфо

CORS — це механізм безпеки браузера. Він захищає користувачів від шкідливих сайтів, що намагаються робити запити від їхнього імені. JSONPlaceholder дозволяє CORS для всіх, тому проблем з ним не буде. У реальних проектах CORS налаштовує backend-розробник.


Підсумок

  • fetch(url) робить HTTP-запит, повертає Promise з Response
  • response.json() парсить відповідь як JSON (теж Promise)
  • GET — отримати, POST — створити, PUT/PATCH — оновити, DELETE — видалити
  • POST/PUT/PATCH потребують method, headers та body: JSON.stringify(data)
  • response.ok перевіряє, чи статус 200-299 (fetch не кидає помилку при 404/500!)
  • JSONPlaceholder — безкоштовне API для практики та тестування

Що далі?

У наступному уроці — ES6 Modules: import/export, організація коду у модулі, named та default exports.

Інфо

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