Работа с Fetch JS: основы и продвинутые техники

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

Работа с Fetch JS: основы и продвинутые техники

Что такое Fetch API и зачем он нужен

Главные преимущества Fetch перед его предшественником:

Промис-ориентированный подход: работа строится на цепочках .then() и .catch(), что избавляет от «ада колбэков».

  • Более чистый и читаемый синтаксис: код становится структурированным и предсказуемым.
  • Гибкая настройка запросов: через объект конфигурации init.
  • Встроенная работа с современными стандартами, такими как FormData, Blob, Streams.

Пример 1

Получение данных с помощью Fetch.

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

Базовый синтаксис и структура fetch-запроса

Базовый синтаксис метода fetch() довольно прост: он принимает два аргумента. Первый, обязательный — это URL ресурса, который нужно получить. Второй, необязательный — объект настроек (options), где можно указать метод запроса, заголовки, тело и другие параметры. Сам метод всегда возвращает Promise, который разрешается в объект Response — даже если сервер вернул ошибку, например, 404 или 500.

Стандартная структура запроса включает в себя несколько этапов: инициализация, обработка ответа (с проверкой статуса), преобразование данных и обработка возможных ошибок. Важно понимать, что промис, возвращаемый fetch, не перейдет в состояние rejected при HTTP-ошибках (например, 404). Он завершится успешно, а свойство response.ok будет равно false. Отвергнется промис только при сбое сети или если не удалось совершить запрос.

Пример 2

GET-запрос с явной проверкой статуса.

fetch('https://api.example.com/users/1')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP ошибка! Статус: ${response.status}`);
    }
    return response.json();
  })
  .then(user => console.log(user))
  .catch(error => console.error('Проблема с fetch запросом:', error));

Объект настроек Options

Для полноценного взаимодействия с сервером недостаточно только URL. Объект настроек (options) в качестве второго аргумента fetch() позволяет детально контролировать все аспекты запроса. Это ключевой инструмент для отправки POST, PUT, DELETE запросов, передачи заголовков авторизации, кук и других данных.

Основные свойства объекта options:

  • method: строка с HTTP-методом ('GET', 'POST', 'PUT', 'DELETE' и др.).
  • headers: объект с HTTP-заголовками (например, {'Content-Type': 'application/json'}).
  • body: данные для отправки (строка, FormData, Blob, ArrayBuffer и т.д.). Для методов GET и HEAD не используется.
  • mode: режим запроса ('cors', 'no-cors', 'same-origin').
  • credentials: управление передачей кук и учетных данных ('omit', 'same-origin', 'include').
  • cache: политика кеширования ('default', 'no-store', 'reload' и др.).

Пример 3

POST-запрос с отправкой JSON.

fetch('https://api.example.com/posts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json;charset=utf-8',
    'Authorization': 'Bearer YOUR_TOKEN'
  },
  body: JSON.stringify({
    title: 'Новый пост',
    body: 'Содержимое поста'
  })
})
.then(response => response.json())
.then(post => console.log('Создано:', post));

Обработка ответа: Response и его методы

После выполнения запроса мы получаем объект Response. Этот объект содержит всю информацию об ответе сервера: статус, заголовки и тело. Прежде чем извлечь данные из тела, необходимо использовать соответствующий метод для его преобразования, так как тело ответа является ReadableStream.

Основные методы для чтения тела ответа (каждый можно вызвать только один раз):

  • response.json(): парсит тело как JSON, возвращает промис с JavaScript-объектом.
  • response.text(): возвращает тело как текст.
  • response.blob(): возвращает тело как Blob (бинарные данные, например, изображение).
  • response.formData(): возвращает тело как объект FormData.
  • response.arrayBuffer(): возвращает тело как ArrayBuffer (низкоуровневое представление бинарных данных).

Важные свойства объекта Response:

  • ok: булево значение, true если статус ответа в диапазоне 200-299.
  • status: числовой код статуса HTTP (200, 404, 500 и т.д.).
  • statusText: текстовое сообщение статуса ('OK', 'Not Found').
  • headers: объект с заголовками ответа.

Пример 4

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

fetch('https://api.example.com/avatar.jpg')
  .then(response => response.blob())
  .then(blob => {
    const imageUrl = URL.createObjectURL(blob);
    document.getElementById('avatar').src = imageUrl;
  });

fetch('https://api.example.com/data.csv')
  .then(response => response.text())
  .then(csvText => {
    console.log('CSV данные:', csvText);
  });
Из ноутбука появляются летающие светящиеся экраны

Оживи свой сайт. Освой JavaScript!

Статичная верстка — это только скелет. Наш онлайн-курс "JavaScript с нуля до профи" даст твоим страницам мышцы и нервы. Научись создавать слайдеры, формы, интерактивные карты и получать данные с сервера.

От теории — к реальным скриптам в твоём портфолио.

Подробнее о курсе

Работа с заголовками, кеширование и прерывание запроса

Для создания профессиональных приложений базовых знаний о Fetch недостаточно. Продвинутые техники включают в себя детальную работу с заголовками, управление кешированием и возможность прервать долгий запрос.

Работа с заголовками осуществляется через объекты Headers. Их можно передавать в настройках запроса и читать из ответа.

Пример 5

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

const myHeaders = new Headers();
myHeaders.append('Content-Type', 'application/json');
myHeaders.append('X-Custom-Header', 'value');

// Чтение заголовков ответа
fetch('https://api.example.com/')
  .then(response => {
    const contentType = response.headers.get('content-type');
    if (contentType && contentType.includes('application/json')) {
      return response.json();
    }
    throw new TypeError('Ожидался JSON!');
  });

Управление кешированием происходит через опцию cache. Это критически важно для производительности.

Пример 6

Управление кэшированием.


// Всегда запрашивать свежие данные с сервера, игнорируя кеш браузера и HTTP-кеш
fetch('https://api.example.com/data', {
  cache: 'no-store'
});

// Использовать кеш, но проверить обновления на сервере (как при перезагрузке страницы Ctrl+F5)
fetch('https://api.example.com/data', {
  cache: 'reload'
});

Прерывание запроса (AbortController) — это мощный механизм для отмены fetch-запроса, если он больше не нужен (например, пользователь ушел со страницы или вводит новый поисковый запрос).

Пример 7

Прерывание запроса.

const controller = new AbortController();
const signal = controller.signal;

// Запускаем запрос с сигналом
fetch('https://api.example.com/large-data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Запрос был прерван');
    } else {
      console.error('Другая ошибка:', err);
    }
  });

// Прерываем запрос через 5 секунд (или по клику пользователя)
setTimeout(() => {
  controller.abort();
}, 5000);

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

Надежная обработка ошибок в Fetch API требует комплексного подхода, так как промис не переходит в состояние rejected при HTTP-ошибках. Необходимо обрабатывать как сбои сети, так и «неуспешные» ответы от сервера.

Основные типы ошибок, которые нужно учитывать:

  • Сетевые ошибки: промис будет отвергнут, выполнится блок .catch().
  • Ошибки HTTP (4xx, 5xx): промис будет выполнен, но response.ok будет false.
  • Ошибки парсинга тела ответа: например, если вызвать .json() для ответа, который не является валидным JSON.

Пример 8

Правильный паттерн обработки ошибок.

async function fetchData(url) {
  try {
    const response = await fetch(url);
    // Проверяем, успешен ли HTTP-статус
    if (!response.ok) {
      // Можно попробовать прочитать тело ошибки от сервера
      const errorText = await response.text();
      throw new Error(`Запрос завершился с ошибкой ${response.status}: ${errorText}`);
    }
    // Пытаемся распарсить JSON
    const data = await response.json();
    return data;
  } catch (error) {
    // Ловим любые ошибки: сетевые, парсинга или выброшенные нами
    console.error('Не удалось загрузить данные:', error.message);
    // Пробрасываем ошибку дальше или возвращаем значение по умолчанию
    throw error;
  }
}

// Использование
fetchData('https://api.example.com/broken-endpoint')
  .then(data => console.log(data))
  .catch(() => {
    console.log('Показываем пользователю сообщение об ошибке');
  });

Практические примеры и лайфхаки

В реальных проектах практические примеры и лайфхаки с использованием Fetch API помогают решать типовые задачи эффективно.

1. Отправка данных формы (FormData)

Идеально подходит для отправки файлов и данных формы без ручного формирования multipart/form-data.

Пример 9

Отправка данных формы.

<form id="user-form">
  <input type="text" name="username" required>
  <input type="email" name="email" required>
  <input type="file" name="avatar">
  <button type="submit">Отправить</button>
</form>

<script>
  document.getElementById('user-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);

    const response = await fetch('https://api.example.com/profile', {
      method: 'POST',
      body: formData
      // Заголовок 'Content-Type' НЕ устанавливаем вручную! Браузер сделает это сам с правильным boundary.
    });
    const result = await response.json();
    console.log(result);
  });
</script>

2. Параллельное выполнение запросов (Promise.all)

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

Пример 10

Загрузка данных с нескольких эндпоинтов.

async function fetchUserAndPosts(userId) {
  const [userResponse, postsResponse] = await Promise.all([
    fetch(`https://api.example.com/users/${userId}`),
    fetch(`https://api.example.com/users/${userId}/posts`)
  ]);

  // Проверяем оба ответа на ошибки
  if (!userResponse.ok || !postsResponse.ok) {
    throw new Error('Один из запросов не удался');
  }

  const user = await userResponse.json();
  const posts = await postsResponse.json();

  return { user, posts };
}

fetchUserAndPosts(1)
  .then(data => {
    console.log('Пользователь:', data.user);
    console.log('Посты:', data.posts);
  });

3. Лайфхак: создание фабрики/обертки для запросов

Чтобы избежать дублирования кода (базовый URL, заголовки авторизации, обработка ошибок).

Пример 11

Создание обертки для запросов.

class ApiClient {
  constructor(baseUrl, defaultHeaders = {}) {
    this.baseUrl = baseUrl;
    this.defaultHeaders = defaultHeaders;
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    const headers = { ...this.defaultHeaders, ...options.headers };

    const response = await fetch(url, { ...options, headers });

    if (!response.ok) {
      const error = new Error(`API Error: ${response.status}`);
      error.status = response.status;
      throw error;
    }

    // Предполагаем, что ответ всегда JSON. Можно сделать адаптивнее.
    return response.json();
  }

  get(endpoint) {
    return this.request(endpoint);
  }

  post(endpoint, body) {
    return this.request(endpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body)
    });
  }
}

// Использование
const api = new ApiClient('https://api.example.com', { 'X-API-Key': 'secret' });
api.get('/users').then(users => console.log(users));
api.post('/users', { name: 'Анна' }).then(newUser => console.log(newUser));

Часто задаваемые вопросы о Fetch API

Чем Fetch API лучше старых XMLHttpRequest (XHR)?

Fetch API предоставляет более чистый, основанный на промисах синтаксис, который избавляет от «ада колбэков» и делает код более линейным и читаемым. Он также предлагает более гибкую и логичную архитектуру (например, отдельные объекты для Request и Response), лучше интегрируется с современными веб-стандартами (Streams, Service Workers) и по умолчанию не отправляет куки, что безопаснее.

Почему fetch не обрабатывает HTTP-ошибки (404, 500) как rejection в Promise?

Это дизайнерское решение, основанное на философии, что получение ответа от сервера (даже с ошибкой) является успешным выполнением сетевой операции. Сбой сети или невозможность отправить запрос — это истинная ошибка (rejection). Для обработки HTTP-ошибок необходимо вручную проверять свойства ответа, например, response.ok или response.status.

Как отправить данные формы (form data) с файлом через fetch?

Для этого нужно использовать объект FormData. Просто передайте ему HTML-форму (DOM-элемент) как аргумент, а затем используйте этот объект в качестве тела (body) запроса. Важный нюанс: не устанавливайте заголовок Content-Type вручную — браузер сделает это автоматически с правильной границей (boundary) для multipart/form-data.

Как правильно настроить CORS при работе с fetch?

Настройка CORS должна выполняться на стороне сервера путем добавления правильных заголовков ответа, таких как Access-Control-Allow-Origin. Со стороны клиента (в коде на JavaScript) вы можете лишь контролировать режим запроса с помощью опции mode: 'cors' (по умолчанию) и управлять отправкой учетных данных с помощью credentials: 'include', если сервер их разрешает.

Можно ли отменить fetch-запрос?

Да, для отмены запросов используется встроенный браузерный API AbortController. Создайте контроллер, передайте его сигнал (signal) в опции fetch, а затем для отмены вызовите метод abort() у этого контроллера. Отмененный запрос выбросит ошибку с именем AbortError.

Fetch или Axios: что лучше использовать?

Выбор зависит от проекта. Fetch — это нативный API, он не требует установки дополнительных библиотек и обладает хорошей базовой функциональностью. (откроется в новой вкладке)Axios— это сторонняя библиотека, которая предоставляет более удобный синтаксис (например, автоматическую обработку JSON, преобразование данных), встроенную отмену запросов (до появления AbortController) и лучшую кросс-браузерную совместимость для старых сред.

Как отправить запрос с авторизацией (JWT-токен, Basic Auth)?

Токен или данные для базовой авторизации передаются через HTTP-заголовок, обычно Authorization. Добавьте этот заголовок в объект настроек fetch. Например, для JWT: headers: { 'Authorization': 'Bearer YOUR_JWT_TOKEN' }. Для Basic Auth можно передать логин и пароль в той же строке, закодированные в base64.

Почему мой fetch-запрос не отправляет куки (cookie)?

По умолчанию fetch не отправляет и не принимает куки, следуя политике безопасности. Чтобы включить передачу учетных данных, необходимо явно установить опцию credentials в значение 'include' (для кросс-доменных запросов) или 'same-origin' (для запросов в пределах одного домена). Сервер также должен ответить с соответствующим заголовком Access-Control-Allow-Credentials: true.

Заключение

Освоив Fetch API, вы сделали серьезный шаг в развитии своих навыков современного JavaScript-разработчика. Однако это лишь один из множества мощных инструментов в экосистеме языка.

Если вы хотите не просто фрагментарно изучить тему запросов, а системно выстроить знания от основ переменных и функций до сложных архитектурных решений и фреймворков, обратите внимание на наш онлайн-курс «Обучение JavaScript с нуля до профи». Он предоставит вам структурированный путь, поддержку опытного наставника и реальные проекты для портфолио, что превратит ваше обучение из хаотичного поиска информации в уверенное движение к цели.

А если вам нужен не просто навык, а готовый, профессионально выполненный продукт — наша веб-студия готова воплотить вашу идею в жизнь. Мы создаем сайты и веб-приложения, которые эффективно решают задачи бизнеса. Свяжитесь с нами для бесплатной консультации, и мы вместе обсудим ваш проект.

Теги: