Fetch API

Fetch API представляет собой современный интерфейс браузерного JavaScript для выполнения HTTP-запросов. Он заменяет устаревший XMLHttpRequest и обеспечивает более гибкую, цепочечную и читаемую модель работы с сетевыми запросами.

Ключевая особенность fetch заключается в том, что он основан на Promise, что позволяет использовать его вместе с async/await и строить асинхронный код без вложенных колбэков.

Базовый синтаксис:

fetch(url, options)
  • url — адрес ресурса
  • options — объект конфигурации запроса (необязательный параметр)

Простейший GET-запрос

Самый распространённый сценарий использования — получение данных с сервера.

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('Ошибка запроса:', error);
  });

Важный момент: fetch не отклоняет Promise при HTTP-ошибках (например, 404 или 500). Он завершает запрос успешно, и проверка статуса выполняется вручную.


Объект Response

Результатом выполнения fetch является объект Response, который содержит:

  • status — HTTP-код ответа

  • ok — булево значение успешности (true для 200–299)

  • headers — заголовки ответа

  • методы для получения тела ответа:

    • json()
    • text()
    • blob()
    • formData()
    • arrayBuffer()

Пример проверки статуса:

fetch('/api/user')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP ошибка: ${response.status}`);
    }
    return response.json();
  })
  .then(user => console.log(user))
  .catch(err => console.error(err));

Использование async/await

Современный способ работы с fetch строится на async/await, что делает код линейным и читаемым.

async function getData() {
  try {
    const response = await fetch('/api/data');

    if (!response.ok) {
      throw new Error(`Ошибка: ${response.status}`);
    }

    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Ошибка загрузки:', error);
  }
}

Передача параметров запроса

Query-параметры

GET-запросы часто используют строку запроса:

fetch('/api/search?query=leaflet&limit=10')
  .then(res => res.json())
  .then(data => console.log(data));

Для динамического формирования параметров удобно использовать URLSearchParams:

const params = new URLSearchParams({
  query: 'leaflet',
  limit: 10
});

fetch(`/api/search?${params.toString()}`)
  .then(res => res.json())
  .then(data => console.log(data));

POST-запрос и отправка данных

Для отправки данных используется параметр options.

fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Ivan',
    age: 30
  })
})
  .then(res => res.json())
  .then(data => console.log(data));

Ключевые элементы:

  • method — HTTP-метод
  • headers — заголовки запроса
  • body — тело запроса (строка, FormData и т.д.)

Работа с заголовками

Объект Headers позволяет управлять HTTP-заголовками:

const headers = new Headers();
headers.append('Authorization', 'Bearer token123');
headers.append('Accept', 'application/json');

fetch('/api/private', {
  headers
});

Также можно передавать объект:

fetch('/api/private', {
  headers: {
    Authorization: 'Bearer token123'
  }
});

Отправка FormData

FormData используется для отправки форм и файлов:

const formData = new FormData();
formData.append('username', 'Ivan');
formData.append('avatar', fileInput.files[0]);

fetch('/api/upload', {
  method: 'POST',
  body: formData
});

Важно: при использовании FormData заголовок Content-Type устанавливается автоматически.


Обработка различных типов ответа

JSON

const data = await response.json();

Текст

const text = await response.text();

Blob (файлы, изображения)

const blob = await response.blob();

const url = URL.createObjectURL(blob);

ArrayBuffer (низкоуровневые данные)

const buffer = await response.arrayBuffer();

Клонирование ответа

Тело ответа можно прочитать только один раз. Для повторного использования применяется clone():

const responseClone = response.clone();

const data1 = await response.json();
const data2 = await responseClone.json();

Таймаут запросов

Fetch API не поддерживает таймаут напрямую, но его можно реализовать через AbortController.

const controller = new AbortController();

const timeout = setTimeout(() => {
  controller.abort();
}, 5000);

try {
  const response = await fetch('/api/data', {
    signal: controller.signal
  });

  clearTimeout(timeout);

  const data = await response.json();
  console.log(data);
} catch (err) {
  if (err.name === 'AbortError') {
    console.log('Запрос прерван по таймауту');
  }
}

Повторные запросы и retry-логика

Для нестабильных сетей используется повтор выполнения запроса:

async function fetchWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url);

      if (!response.ok) {
        throw new Error('Ошибка HTTP');
      }

      return await response.json();
    } catch (e) {
      if (i === retries - 1) throw e;
    }
  }
}

Кэширование запросов

Браузер может кэшировать запросы, а поведение управляется через cache:

fetch('/api/data', {
  cache: 'no-cache'
});

Возможные значения:

  • default
  • no-store
  • reload
  • no-cache
  • force-cache
  • only-if-cached

CORS и ограничения безопасности

Fetch API подчиняется политике CORS (Cross-Origin Resource Sharing). Запросы на другой домен требуют разрешения со стороны сервера.

Типичные ошибки:

  • отсутствие заголовка Access-Control-Allow-Origin
  • блокировка preflight-запроса OPTIONS
  • отсутствие разрешённых методов

Работа с credentials

Для отправки cookies и авторизационных данных:

fetch('/api/profile', {
  credentials: 'include'
});

Варианты:

  • same-origin (по умолчанию)
  • include
  • omit

Параллельные запросы

Несколько запросов можно выполнять одновременно через Promise.all:

const [users, posts] = await Promise.all([
  fetch('/api/users').then(r => r.json()),
  fetch('/api/posts').then(r => r.json())
]);

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

fetch не ловит HTTP-ошибки автоматически, но ловит сетевые сбои:

fetch('/api/data')
  .then(res => {
    if (!res.ok) throw new Error('HTTP error');
    return res.json();
  })
  .catch(err => {
    console.error('Сетевая ошибка или сбой:', err);
  });

Интеграция с архитектурой клиентских приложений

В прикладных интерфейсах Fetch API часто абстрагируется в сервисный слой:

const api = {
  async getUsers() {
    const res = await fetch('/api/users');
    if (!res.ok) throw new Error('Ошибка загрузки пользователей');
    return res.json();
  },

  async createUser(data) {
    const res = await fetch('/api/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });

    if (!res.ok) throw new Error('Ошибка создания пользователя');
    return res.json();
  }
};

Ограничения Fetch API

Несмотря на гибкость, имеются особенности:

  • отсутствие встроенного таймаута
  • отсутствие автоматической обработки HTTP-ошибок
  • необходимость ручной сериализации тела запроса
  • CORS-ограничения
  • невозможность отслеживания прогресса загрузки (без дополнительных API)

Сравнение с XMLHttpRequest

  • fetch использует Promise, XMLHttpRequest — колбэки
  • fetch имеет более чистый синтаксис
  • fetch не поддерживает прогресс загрузки из коробки
  • fetch не прерывает запрос при HTTP-ошибках автоматически

Использование в реальных приложениях

В клиентских приложениях Fetch API становится базовым слоем взаимодействия с сервером. Поверх него строятся:

  • сервисы API
  • кэш-слои
  • обработчики ошибок
  • retry-механизмы
  • синхронизация состояния интерфейса

Типичная архитектура:

UI слой → сервис API → fetch → сервер

Такой подход позволяет изолировать сетевую логику от компонентов интерфейса и повысить тестируемость кода.