← Back to course

Fetch API

Fetch API

С возвращением.

В предыдущем уроке ты познакомился с local storage.

Local storage позволяет JavaScript запоминать маленькие кусочки данных в браузере.

Очень полезно.

Очень практично.

Очень в стиле: “у браузера теперь есть маленький блокнот.”

Сегодня мы научим JavaScript получать данные извне страницы.

И тут всё становится очень реальным.

Веб-сайты редко держат все данные в одном JavaScript-файле.

Они часто загружают данные из:

Чтобы получить эти данные, JavaScript использует Fetch API.

Fetch позволяет JavaScript попросить данные.

Браузер говорит:

Я пойду и принесу.

JavaScript отвечает:

Хорошо. Только, пожалуйста, не возвращайся с хаосом.

Иногда он возвращается с данными.

Иногда — с ошибкой.

Иногда — с чем-то, что заставляет тебя переосмыслить жизненные решения.

Добро пожаловать в веб-разработку.

Что Ты Изучишь

В этом уроке ты изучишь:

В конце этого урока твоя страница будет загружать данные из JSON-файла и показывать их на экране.

Это огромный шаг.

До этого большинство данных жило прямо внутри JavaScript.

Теперь JavaScript начнёт брать данные снаружи.

Как frontend-посланник.

В хорошей обуви.

Что Такое Fetch?

fetch() — это JavaScript-функция для запроса данных.

Пример:

fetch("products.json");

Это просит браузер загрузить файл products.json.

Но есть важный момент.

fetch() не возвращает данные мгновенно.

Это занимает время.

Даже если файл маленький.

Даже если файл локальный.

Даже если ты просишь очень вежливо.

Поэтому JavaScript воспринимает fetch() как асинхронную операцию.

Это означает:

Начни запрос сейчас.
Продолжай, когда результат будет готов.

Это важно.

Очень важно.

Интернет не мгновенный.

Даже когда притворяется таким.

Что Такое JSON?

JSON означает JavaScript Object Notation.

Это текстовый формат для хранения и передачи данных.

Он похож на JavaScript-объекты.

Пример JSON:

{
  "name": "JavaScript Course",
  "price": 49,
  "available": true
}

JSON повсюду.

API используют JSON.

Backend-ы возвращают JSON.

Frontend-приложения читают JSON.

Твой будущий backend на Java Spring Boot, скорее всего, тоже будет отдавать JSON.

А frontend будет пить его как кофе.

Осторожно.

Потому что плохой JSON приносит грусть.

Создай Проект

Создай папку для этого урока:

mkdir javascript-lesson11
cd javascript-lesson11
touch index.html
touch script.js
touch products.json

Твой проект должен выглядеть так:

javascript-lesson11/
  index.html
  script.js
  products.json

Важно:

В этом уроке не открывай index.html напрямую двойным кликом.

Потому что fetch() может неправильно загружать локальные файлы через file://.

Используй локальный сервер.

Например, с Caddy:

caddy file-server --listen :8080

Потом открой:

http://localhost:8080

Теперь браузер сможет нормально загрузить JSON-файл.

Без framework-дракона.

Просто маленький локальный сервер.

Цивилизованно.

Напиши JSON-Файл

Открой products.json и добавь:

[
  {
    "id": 1,
    "name": "JavaScript Course",
    "price": 49,
    "available": true
  },
  {
    "id": 2,
    "name": "HTML Course",
    "price": 29,
    "available": true
  },
  {
    "id": 3,
    "name": "CSS Course",
    "price": 39,
    "available": false
  }
]

Это массив объектов.

Каждый объект представляет один товар.

Каждый товар имеет:

Это очень распространено.

Список товаров?

Массив объектов.

Список пользователей?

Массив объектов.

Список постов блога?

Массив объектов.

JavaScript видит массивы объектов повсюду.

Как голубей на площади.

Напиши HTML

Открой index.html и добавь:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Fetch API</title>
</head>
<body>
  <h1>Fetch API</h1>

  <button id="loadButton">Загрузить Товары</button>

  <p id="message">Нажми кнопку, чтобы загрузить товары.</p>

  <ul id="productList"></ul>

  <script src="script.js"></script>
</body>
</html>

У нас есть:

Просто.

Чисто.

Готово получать данные.

Страница ждёт.

Как официант с пустым подносом.

Твой Первый Fetch

Открой script.js и добавь:

const loadButton = document.getElementById("loadButton");
const messageElement = document.getElementById("message");
const productListElement = document.getElementById("productList");

async function loadProducts() {
  const response = await fetch("products.json");
  const products = await response.json();

  console.log(products);
}

loadButton.addEventListener("click", loadProducts);

Запусти локальный сервер:

caddy file-server --listen :8080

Открой:

http://localhost:8080

Нажми кнопку.

Открой консоль.

Ты должен увидеть массив товаров.

Поздравляю.

JavaScript загрузил данные из JSON-файла.

Это реально.

Маленькое реально.

Но реально.

Понимание async и await

Посмотри на эту функцию:

async function loadProducts() {
  const response = await fetch("products.json");
  const products = await response.json();

  console.log(products);
}

Слово async означает:

Эта функция выполняет асинхронную работу.

Слово await означает:

Подожди, пока эта операция завершится, перед тем как двигаться дальше.

Эта строка:

const response = await fetch("products.json");

ждёт, пока файл загрузится.

Эта строка:

const products = await response.json();

ждёт, пока response преобразуется в JavaScript-данные.

Без await JavaScript не ждёт.

А потом код становится растерянным.

Как пытаться есть пасту до того, как она сварилась.

Технически возможно.

Эмоционально неправильно.

Что Такое response?

fetch() даёт нам объект response.

Пример:

const response = await fetch("products.json");

Response содержит информацию о запросе.

Это ещё не финальные данные.

Чтобы получить JSON-данные, мы используем:

const products = await response.json();

Это преобразует тело response в JavaScript-данные.

Важно:

response.json() тоже асинхронный.

Поэтому мы используем await.

JavaScript нужен момент, чтобы прочитать и распарсить JSON.

Даже JavaScript нуждается в минутке.

Уважай процесс.

Показ Товаров на Странице

Теперь покажем товары в DOM.

Обнови script.js:

const loadButton = document.getElementById("loadButton");
const messageElement = document.getElementById("message");
const productListElement = document.getElementById("productList");

async function loadProducts() {
  const response = await fetch("products.json");
  const products = await response.json();

  productListElement.innerHTML = "";

  for (const product of products) {
    const listItem = document.createElement("li");
    listItem.textContent = `${product.name} - €${product.price}`;
    productListElement.appendChild(listItem);
  }

  messageElement.textContent = `Загружено товаров: ${products.length}.`;
}

loadButton.addEventListener("click", loadProducts);

Обнови браузер.

Нажми кнопку.

Теперь товары появляются на странице.

Что произошло?

JavaScript:

Это frontend development.

Данные приходят.

DOM обновляется.

Пользователь видит контент.

Красиво.

Немного драматично.

Но красиво.

Добавь Статус Доступности

Теперь покажем, доступен ли товар.

Замени эту строку:

listItem.textContent = `${product.name} - €${product.price}`;

на эту:

const status = product.available ? "Доступен" : "Недоступен";
listItem.textContent = `${product.name} - €${product.price} - ${status}`;

Полный цикл:

for (const product of products) {
  const listItem = document.createElement("li");
  const status = product.available ? "Доступен" : "Недоступен";

  listItem.textContent = `${product.name} - €${product.price} - ${status}`;
  productListElement.appendChild(listItem);
}

Это использует ternary operator.

Он означает:

Если product.available true, используй "Доступен".
Иначе используй "Недоступен".

Коротко.

Полезно.

В начале немного подозрительно.

Но после практики дружелюбно.

Добавь Loading State

Загрузка данных занимает время.

Поэтому стоит показать сообщение о загрузке.

Обнови loadProducts:

async function loadProducts() {
  messageElement.textContent = "Загрузка товаров...";
  productListElement.innerHTML = "";

  const response = await fetch("products.json");
  const products = await response.json();

  for (const product of products) {
    const listItem = document.createElement("li");
    const status = product.available ? "Доступен" : "Недоступен";

    listItem.textContent = `${product.name} - €${product.price} - ${status}`;
    productListElement.appendChild(listItem);
  }

  messageElement.textContent = `Загружено товаров: ${products.length}.`;
}

Теперь после клика страница говорит:

Загрузка товаров...

Это хороший user experience.

Пользователи любят знать, что что-то происходит.

Иначе они кликают снова.

И снова.

И снова.

Потом твоё приложение получает 47 запросов и начинает плакать.

Обработка Ошибок через try и catch

Иногда fetch не удаётся.

Возможно, неправильное имя файла.

Возможно, сервер не запущен.

Возможно, интернет переживает философский кризис.

Нам нужно обрабатывать ошибки.

Обнови script.js:

const loadButton = document.getElementById("loadButton");
const messageElement = document.getElementById("message");
const productListElement = document.getElementById("productList");

async function loadProducts() {
  messageElement.textContent = "Загрузка товаров...";
  productListElement.innerHTML = "";

  try {
    const response = await fetch("products.json");
    const products = await response.json();

    for (const product of products) {
      const listItem = document.createElement("li");
      const status = product.available ? "Доступен" : "Недоступен";

      listItem.textContent = `${product.name} - €${product.price} - ${status}`;
      productListElement.appendChild(listItem);
    }

    messageElement.textContent = `Загружено товаров: ${products.length}.`;
  } catch (error) {
    messageElement.textContent = "Не удалось загрузить товары.";
    console.log(error);
  }
}

loadButton.addEventListener("click", loadProducts);

Теперь ошибки не разрушают атмосферу молча.

Если что-то пойдёт не так, пользователь увидит сообщение.

А разработчик увидит ошибку в консоли.

Все получают информацию.

Очень здорово.

Очень редко.

Проверка response.ok

Есть ещё один важный момент.

fetch() не всегда бросает ошибку при плохих HTTP-ответах.

Например, если файл не существует, сервер может вернуть 404.

Поэтому стоит проверить:

if (!response.ok) {
  throw new Error("Не удалось загрузить товары.");
}

Обнови часть с fetch:

const response = await fetch("products.json");

if (!response.ok) {
  throw new Error("Не удалось загрузить товары.");
}

const products = await response.json();

Полная функция:

async function loadProducts() {
  messageElement.textContent = "Загрузка товаров...";
  productListElement.innerHTML = "";

  try {
    const response = await fetch("products.json");

    if (!response.ok) {
      throw new Error("Не удалось загрузить товары.");
    }

    const products = await response.json();

    for (const product of products) {
      const listItem = document.createElement("li");
      const status = product.available ? "Доступен" : "Недоступен";

      listItem.textContent = `${product.name} - €${product.price} - ${status}`;
      productListElement.appendChild(listItem);
    }

    messageElement.textContent = `Загружено товаров: ${products.length}.`;
  } catch (error) {
    messageElement.textContent = "Не удалось загрузить товары.";
    console.log(error);
  }
}

Теперь код сильнее.

Не бессмертный.

Но сильнее.

Как кофе после второй чашки.

Добавь Лучшие Стили

Сделаем страницу красивее.

Обнови index.html:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Fetch API</title>

  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 800px;
      margin: 60px auto;
      padding: 0 24px;
      background-color: #f3f4f6;
      color: #111827;
    }

    .card {
      background-color: white;
      padding: 24px;
      border: 2px solid #e5e7eb;
      border-radius: 18px;
    }

    h1 {
      font-size: 42px;
    }

    button {
      background-color: #2563eb;
      color: white;
      border: none;
      padding: 14px 20px;
      border-radius: 999px;
      font-weight: 700;
      cursor: pointer;
    }

    button:hover {
      background-color: #1d4ed8;
    }

    .message {
      margin-top: 20px;
      font-size: 18px;
      font-weight: 700;
    }

    .products {
      list-style: none;
      padding: 0;
      margin-top: 20px;
      display: grid;
      gap: 12px;
    }

    .product {
      padding: 16px;
      border: 2px solid #e5e7eb;
      border-radius: 14px;
      background-color: #f9fafb;
    }

    .product strong {
      display: block;
      font-size: 22px;
      margin-bottom: 6px;
    }

    .available {
      color: #166534;
      font-weight: 700;
    }

    .unavailable {
      color: #991b1b;
      font-weight: 700;
    }
  </style>
</head>
<body>
  <h1>Fetch API</h1>

  <div class="card">
    <button id="loadButton">Загрузить Товары</button>

    <p id="message" class="message">Нажми кнопку, чтобы загрузить товары.</p>

    <ul id="productList" class="products"></ul>
  </div>

  <script src="script.js"></script>
</body>
</html>

Теперь обнови показ товаров в script.js:

for (const product of products) {
  const listItem = document.createElement("li");
  const status = product.available ? "Доступен" : "Недоступен";
  const statusClass = product.available ? "available" : "unavailable";

  listItem.className = "product";

  listItem.innerHTML = `
    <strong>${product.name}</strong>
    <span>Цена: €${product.price}</span><br>
    <span class="${statusClass}">${status}</span>
  `;

  productListElement.appendChild(listItem);
}

Теперь товары выглядят как карточки.

Намного лучше.

Меньше “сырой HTML из пещеры.”

Больше “маленькое frontend-приложение для начинающих.”

Прогресс.

Полный JavaScript Код

Вот полный script.js:

const loadButton = document.getElementById("loadButton");
const messageElement = document.getElementById("message");
const productListElement = document.getElementById("productList");

async function loadProducts() {
  messageElement.textContent = "Загрузка товаров...";
  productListElement.innerHTML = "";

  try {
    const response = await fetch("products.json");

    if (!response.ok) {
      throw new Error("Не удалось загрузить товары.");
    }

    const products = await response.json();

    for (const product of products) {
      const listItem = document.createElement("li");
      const status = product.available ? "Доступен" : "Недоступен";
      const statusClass = product.available ? "available" : "unavailable";

      listItem.className = "product";

      listItem.innerHTML = `
        <strong>${product.name}</strong>
        <span>Цена: €${product.price}</span><br>
        <span class="${statusClass}">${status}</span>
      `;

      productListElement.appendChild(listItem);
    }

    messageElement.textContent = `Загружено товаров: ${products.length}.`;
  } catch (error) {
    messageElement.textContent = "Не удалось загрузить товары.";
    console.log(error);
  }
}

loadButton.addEventListener("click", loadProducts);

Теперь у тебя есть:

Это уже серьёзный beginner-проект.

Маленький.

Но серьёзный.

Как маленький espresso.

Типичные Ошибки

Открывать index.html Напрямую

Неправильно:

file:///home/user/javascript-lesson11/index.html

Лучше:

caddy file-server --listen :8080

Потом открой:

http://localhost:8080

Fetch часто требует сервер.

Даже маленький локальный.

Не дерись с браузером.

У него правила.

Много правил.

Забыть await

Неправильно:

const response = fetch("products.json");
const products = response.json();

Правильно:

const response = await fetch("products.json");
const products = await response.json();

Без await у тебя ещё нет данных.

У тебя есть promise.

Promise — это не результат.

Это будущий результат.

Как заказ пиццы.

Чек — это не пицца.

Снова пицца.

Но работает.

Забыть response.json()

Неправильно:

const products = await response;

Правильно:

const products = await response.json();

Response — это не финальные данные.

Нужно преобразовать его в JSON.

JavaScript мощный.

Но ему всё равно нужны инструкции.

Неправильный Путь к Файлу

Если файл называется:

products.json

тогда fetch:

fetch("products.json")

Если файл в папке:

data/products.json

тогда fetch:

fetch("data/products.json")

Пути должны совпадать.

JavaScript не будет искать по папкам как детектив.

Он просто упадёт.

Тихо.

Как кот, который скидывает что-то со стола.

Практика

Создай новый JSON-файл с названием students.json.

Добавь это:

[
  {
    "name": "Anna",
    "level": "Beginner"
  },
  {
    "name": "Marco",
    "level": "Intermediate"
  },
  {
    "name": "Viktor",
    "level": "Beginner"
  }
]

Создай кнопку, которая загружает студентов.

Покажи каждого студента на странице.

Пример результата:

Anna - Beginner
Marco - Intermediate
Viktor - Beginner

Бонус:

Покажи, сколько студентов было загружено.

Очень похоже на товары.

Другие данные.

Та же схема.

Так работает программирование.

Выучи один шаблон.

Используй его везде.

Делай вид, что было сложно.

Очень профессионально.

Мини-Челлендж

Создай маленький каталог курсов.

Создай courses.json с:

Потом используй fetch(), чтобы загрузить курсы и показать их как карточки.

Каждая карточка должна показывать:

Бонус:

Показывай только доступные курсы.

Это означает, что можно использовать:

if (course.available) {
  // show course
}

Этот челлендж соединяет:

Короче говоря, JavaScript становится маленькой фабрикой веб-приложений.

Поздравляю.

И ещё: осторожно.

Фабрикам нужна организация.

Итог

Сегодня ты изучил:

Это огромный шаг.

Твой JavaScript теперь может говорить с внешними данными.

Не только с переменными.

Не только с local storage.

Настоящие файлы.

Потом API.

Потом серверы.

Внешний мир.

JavaScript открыл окно.

Надеюсь, не неправильное.

Следующий Урок

В следующем уроке мы создадим маленький финальный проект.

Мы объединим:

Настоящий JavaScript-проект для начинающих.

Не огромный.

Не страшный.

Но полный.

Final boss приближается.

Принеси кофе.