Fetch

API

Application Programming Interface

это набор правил и протоколов, которые позволяют различным программам общаться друг с другом

Web API — это API, доступный через интернет по протоколу HTTP

{
  "id": 1,
  "title": "Заголовок поста",
  "body": "Текст поста",
  "userId": 1
}
                    
Обмен данным происходит при помощи JSON формата

JSON

текстовый формат представления и обмена структурированными данными


{
  "student": {
    "name": "Иван Петров",
    "age": 15,
    "grade": 9,
    "isStudent": true,
    "skills": ["HTML", "CSS", "JavaScript"],
    "contacts": {
      "email": "ivan@example.com",
      "phone": "+7-900-123-45-67"
    }
  }
}
                    

Основан на принципе представления данных в виде пар «ключ — значение»

JSON универсален и представлен практически во всех языках

Для того, чтобы удобно тестировать API используется

HTTP клиент

программа, которая отправляет HTTP запросы к серверу и получает ответы

Например:

  • Браузер (встроенный)
  • Postman
  • Bruno
  • Insomnia
  • curl (командная строка)

Bruno

Fetch

современный способ выполнения HTTP запросов в JavaScript

Он возвращает Promise и работает асинхронно

Асинхронность

возможность выполнения операций, не блокируя основной поток выполнения программы

JS - однопоточный, если бы все операции выполнялись последовательно, то долгие задачи блокировали бы выполнение остального кода

const timer = setTimeout(() => console.log("Я второй"), 60000)
console.log("Я первый")
                    

Promise

объект в JavaScript, который представляет результат асинхронной операции (который будет известен в будущем)

Состояния Promise

  • pending (ожидание) — операция ещё выполняется
  • fulfilled (успех) — операция завершилась успешно, есть результат
  • rejected (ошибка) — операция завершилась с ошибкой

Основные методы

  • .then(result => { ... }) — выполняется, если операция завершилась успешно.
  • .catch(error => { ... }) — выполняется, если произошла ошибка.
  • .finally(() => { ... }) — выполняется в любом случае (успех или ошибка).
  • 
    const fetchData = new Promise((resolve, reject) => {
      setTimeout(() => {
        const success = true;
        if (success) {
          resolve("Данные успешно получены!");
        } else {
          reject("Ошибка при получении данных");
        }
      }, 2000);
    });
    
    fetchData
      .then(result => {
        console.log(result); // "Данные успешно получены!"
      })
      .catch(error => {
        console.error(error); // "Ошибка при получении данных"
      })
      .finally(() => {
        console.log("Операция завершена");
      });
                        

    Асинхронные функции

    Внутри можно использовать await , который останавливает выполнение внутри async функции, пока Promise не вернётся с результатом

    
    function fetchData() {
      return new Promise(resolve => {
        setTimeout(() => resolve("Данные получены!"), 2000);
      });
    }
    
    async function main() {
      console.log("Первый");
      const result = await fetchData();
      console.log(result, "Второй, несмотря на задержку");
      console.log("Третий");
    }
    
    main();
                        

    Можно, не нужно

    
    function getDataWithThen() {
      return fetch("https://jsonplaceholder.typicode.com/todos/1")
        .then(response => response.json())
        .then(data => {
          console.log("Данные получены:", data);
        })
    }
    
    getDataWithThen();
                            

    Лучше так

    
    async function getDataWithAwait() {
        const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
        const data = await response.json();
        console.log("Данные получены:", data);
    }
    
    getDataWithAwait();
                                

    Response

    Когда мы делаем запрос через fetch, он возвращает объект Response . У этого объекта есть специальные методы для чтения тела ответа.

    .json()

    читает тело ответа и преобразует его в JavaScript-объект/массив

    .text()

    который читает тело ответа (обычно HTML или XML ) и возвращает его как строку

    .blob()

    возвращает данные в виде Blob (Binary Large Object - набор единичек и ноликов ). Обычно используется для работы с файлами, изображениями, видео и аудио

    Обработка ошибок

    Даже если сервер вернул код ошибки HTTP (например, 404 или 500), fetch не бросает исключение автоматически

    Виды ошибок:

    • Сетевые ошибки — когда запрос не дошёл до сервера (например, нет интернета).
    • Ошибки HTTP — когда сервер ответил, но с кодом ошибки (например, 404, 500).

    Обработка ошибок методами Promise

    ни нада

    
    fetch("https://jsonplaceholder.typicode.com/invalid-url")
      .then(response => {
        if (!response.ok) {
          throw new Error(`Ошибка HTTP: ${response.status}`);
        }
        return response.json();
      })
      .then(data => console.log("Данные:", data))
      .catch(error => console.error("Ошибка запроса:", error))
      .finally(() => console.log("Запрос завершён"));
    

    Через конструкцию try-catch-finally

    
    async function fetchData() {
      try {
        const response = await fetch("https://jsonplaceholder.typicode.com/invalid-url");
        if (!response.ok) {
          throw new Error(`Ошибка HTTP: ${response.status}`);
        }
        const data = await response.json();
        console.log("Данные:", data);
      } catch (error) {
        console.error("Ошибка запроса:", error);
      } finally {
        console.log("Запрос завершён");
      }
    }
    
    fetchData();
                        
    
    try {
      // Код, который может вызвать ошибку
    } catch (error) {
      // Код, который выполняется, если произошла ошибка
    } finally {
      // Код, который выполнится в любом случае (ошибка была или нет)
    }
                        

    Рендер данных

    1. Получаем JSON
    2. Создаем HTML элементы
    3. Наполняем данным
      
      async function getUsers() {
        try {
          const response = await fetch("https://jsonplaceholder.typicode.com/users");
          const users = await response.json();
      
          const list = document.getElementById("user-list");
          list.innerHTML = ""; // очищаем список перед вставкой
      
          users.forEach(user => {
            const li = document.createElement("li");
            li.textContent = `${user.name} (${user.email})`;
            list.appendChild(li);
          });
        } catch (error) {
          console.error("Ошибка при загрузке:", error);
        }
      }
      
      getUsers();
                              

      Состояния при загрузке

      Пока данные загружаются с сервера, пользователь не должен видеть пустую страницу — нужно показывать состояние загрузки

      ⏳ Loading

      Запрос выполняется, данные ещё не пришли

      ✅ Success

      Данные успешно получены и отображены

      ❌ Error

      Произошла ошибка при загрузке

      Индикатор загрузки

      Показываем спиннер до получения данных, скрываем после
      
      
      Загрузка...
        
        async function getUsers() {
          const loader = document.getElementById("loader");
          const list = document.getElementById("user-list");
        
          loader.style.display = "block"; // показываем загрузку
        
          try {
            const response = await fetch("https://jsonplaceholder.typicode.com/users");
            const users = await response.json();
        
            users.forEach(user => {
              const li = document.createElement("li");
              li.textContent = user.name;
              list.appendChild(li);
            });
          } catch (error) {
            list.innerHTML = "
      • Ошибка загрузки данных 😢
      • "; } finally { loader.style.display = "none"; // скрываем в любом случае } }

        Skeleton-загрузка

        Вместо спиннера можно показать «скелет» будущего контента — заглушки нужного размера

        
        .skeleton {
          background: linear-gradient(90deg, #e0e0e0 25%, #f0f0f0 50%, #e0e0e0 75%);
          background-size: 200% 100%;
          animation: shimmer 1.5s infinite;
          border-radius: 4px;
          height: 20px;
          margin-bottom: 8px;
        }
        
        @keyframes shimmer {
          0%   { background-position: 200% 0; }
          100% { background-position: -200% 0; }
        }
                
        
        function showSkeleton(list, count = 3) {
          list.innerHTML = Array(count)
            .fill('
      • ') .join(""); }

        CORS

        Cross-Origin Resource Sharing

        механизм безопасности браузера, который ограничивает запросы к другому домену, протоколу или порту

        Origin (источник) — это комбинация протокола, домена и порта
        
        https://mysite.com:443   ← origin вашего сайта
        
        https://api.other.com    ← другой домен  → CORS!
        http://mysite.com        ← другой протокол → CORS!
        https://mysite.com:8080  ← другой порт → CORS!
                

        Почему браузер блокирует?

        Политика одного источника (Same-Origin Policy)

        Браузер защищает пользователя: скрипт с одного сайта не должен тайно читать данные с другого сайта (например, из банка)

        Важно: CORS — это ограничение браузера , не сервера. curl и Postman запросы проходят без ограничений, а браузер — нет
        
        Access to fetch at 'https://api.example.com'
        from origin 'https://mysite.com'
        has been blocked by CORS policy:
        No 'Access-Control-Allow-Origin' header
        is present on the requested resource.
                

        Как это работает

        1. Браузер отправляет запрос

        с заголовком Origin: https://mysite.com

        2. Сервер отвечает

        с заголовком Access-Control-Allow-Origin

        ✅ Разрешено

        
        Access-Control-Allow-Origin: *
        Access-Control-Allow-Origin:
          https://mysite.com
                        

        ❌ Заблокировано

        Заголовок отсутствует или содержит другой origin — браузер блокирует ответ

        Preflight-запрос

        Перед «опасными» запросами браузер автоматически отправляет предварительный запрос OPTIONS , чтобы спросить у сервера разрешение

        
        OPTIONS /api/data HTTP/1.1
        Origin: https://mysite.com
        Access-Control-Request-Method: POST
        Access-Control-Request-Headers: Content-Type
                
        Preflight срабатывает при методах POST / PUT / DELETE или при нестандартных заголовках (например, Authorization )
        Простые GET-запросы preflight не требуют

        Что делать с CORS?

        ✅ Правильно

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

        
        Access-Control-Allow-Origin:
          https://mysite.com
                        

        ⚠️ Для разработки

        Использовать прокси в dev-сервере (Vite, webpack)

        
        // vite.config.js
        server: {
          proxy: {
            '/api': 'http://localhost:3000'
          }
        }
                        
        ❌ Не делать: отключать CORS расширениями браузера — это убирает защиту для всех сайтов