Работа с Fetch API в JavaScript: примеры, методы и приемы кода
Перейти

Работа с Fetch API в JavaScript: примеры, методы и приемы кода

#Web API  #Async/Await  #Fetch API  
Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Для веб-разработчиков, знакомых с JavaScript
  • Для программистов, работающих с асинхронными HTTP-запросами
  • Для специалистов, интересующихся современными методами взаимодействия с серверами

Fetch API — мощный инструмент для веб-разработчиков, который произвел настоящую революцию в мире асинхронных HTTP-запросов. Помните те времена, когда мы боролись с бесконечными колбэками и запутанной конфигурацией XMLHttpRequest? 🚀 Fetch API перевернул игру, предоставив элегантный, промис-ориентированный интерфейс, который значительно упрощает взаимодействие с серверами. Независимо от того, разрабатываете ли вы сложное одностраничное приложение или просто добавляете динамическое поведение на статический сайт, понимание Fetch API — это обязательный навык для каждого уважающего себя JavaScript-разработчика. Давайте погрузимся в мир Fetch API и раскроем его потенциал для создания быстрых, эффективных и надежных веб-приложений.

Что такое Fetch API и почему он заменил XMLHttpRequest

Fetch API представляет собой современный JavaScript-интерфейс для выполнения HTTP-запросов, который построен на Promise-архитектуре. Это встроенный в браузер API, разработанный как альтернатива устаревшему XMLHttpRequest (XHR), с более гибким и мощным набором возможностей.

По сути, Fetch API обеспечивает простой и логичный способ асинхронно получать ресурсы по сети, который легко интегрируется с остальной частью JavaScript-экосистемы.

Алексей Ковалев, технический архитектор

В 2018 году я руководил миграцией корпоративной панели управления с jQuery AJAX на нативный Fetch API. Система выполняла тысячи запросов ежедневно для сбора данных с разных микросервисов. Старый код был громоздким: для каждого запроса требовалось более 15 строк кода с обработкой колбэков, проверкой состояний и обработкой ошибок.

После миграции на Fetch API код стал значительно чище. Цепочки промисов позволили структурировать асинхронные операции последовательно, а использование async/await сделало код еще более читаемым. Производительность выросла на 23%, а время отклика системы уменьшилось на 780 мс. Но что действительно впечатлило команду — сокращение кодовой базы на 34% при расширении функциональности.

Переход на Fetch API позволил нам быстрее внедрять новые функции и значительно упростил поддержку приложения. Спустя три года никто из разработчиков не испытывает ностальгии по XMLHttpRequest.

Рассмотрим ключевые преимущества Fetch API по сравнению с XMLHttpRequest:

Аспект XMLHttpRequest Fetch API
Асинхронность Основан на колбэках Использует Promises и async/await
Синтаксис Многословный, требует много кода Лаконичный, цепочки методов
Обработка ошибок Сложная, через проверку состояний Структурированная через catch
Поддержка стримов Отсутствует Встроенная поддержка Response.body
Поддержка CORS Ограниченная Расширенная, с детальными настройками
Прерывание запросов Нестандартизированное AbortController API

Основные причины перехода от XMLHttpRequest к Fetch API:

  • Промисы вместо колбэков — современная обработка асинхронных операций
  • Модульность и расширяемость — разделение на Request, Response и Headers объекты
  • Потоковая передача данных — возможность работать с частичными ответами
  • Нативная поддержка JSON — встроенные методы для работы с JSON
  • Простота использования — меньше кода для выполнения тех же задач

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

Пошаговый план для смены профессии

Основные методы и настройки fetch для HTTP-запросов

Базовый синтаксис метода fetch() выглядит следующим образом:

JS
Скопировать код
fetch(resource, options)
.then(response => /* обработка ответа */)
.catch(error => /* обработка ошибки */);

Где resource — это URL, к которому выполняется запрос, а options — объект с настройками запроса. Рассмотрим основные параметры конфигурации:

Параметр Описание Значение по умолчанию Примеры значений
method HTTP-метод запроса GET POST, PUT, DELETE, PATCH
headers Заголовки HTTP-запроса {} {'Content-Type': 'application/json'}
body Тело запроса undefined JSON.stringify(data), FormData, Blob
mode Режим CORS cors same-origin, no-cors
credentials Передача куки same-origin include, omit
cache Кэширование default no-cache, reload, force-cache
redirect Обработка редиректов follow error, manual
referrer Реферер запроса client/about:client no-referrer, URL
signal Сигнал для прерывания undefined AbortController.signal

Ключевые объекты, с которыми работает Fetch API:

  • Request — представляет HTTP-запрос
  • Response — представляет HTTP-ответ
  • Headers — представляет HTTP-заголовки
  • Body — предоставляет методы для работы с телом запроса/ответа

Наиболее распространенные методы для работы с данными в ответе:

JS
Скопировать код
// Получение ответа в виде JSON
response.json()

// Получение ответа в виде текста
response.text()

// Получение ответа в виде Blob
response.blob()

// Получение ответа в виде FormData
response.formData()

// Получение ответа в виде ArrayBuffer
response.arrayBuffer()

Важно помнить: методы извлечения тела (json(), text() и т.д.) возвращают промисы и могут быть вызваны только один раз. При повторных вызовах возникнет ошибка, так как тело уже было использовано.

GET и POST запросы с fetch: синтаксис и практика

Самые распространенные типы HTTP-запросов — GET для получения данных и POST для их отправки. Рассмотрим, как правильно реализовать эти операции с помощью Fetch API.

🔍 GET-запрос — самый простой вариант использования fetch:

JS
Скопировать код
// Базовый GET-запрос
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`Ошибка HTTP: ${response.status}`);
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Проблема с запросом:', error));

// GET-запрос с параметрами в URL
const params = new URLSearchParams({
category: 'electronics',
sort: 'price',
order: 'desc'
});

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

✉️ POST-запрос — требует дополнительной конфигурации для отправки данных:

JS
Скопировать код
// POST-запрос с JSON данными
const userData = {
username: 'developer2023',
email: 'dev@example.com',
age: 28
};

fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your_token_here'
},
body: JSON.stringify(userData)
})
.then(response => response.json())
.then(data => console.log('Успешно создан пользователь:', data))
.catch(error => console.error('Ошибка:', error));

Более современный подход с использованием async/await делает код еще чище и понятнее:

JS
Скопировать код
// GET-запрос с async/await
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');

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

const data = await response.json();
return data;
} catch (error) {
console.error('Проблема с получением данных:', error);
throw error; // Пробрасываем ошибку дальше
}
}

// POST-запрос с async/await
async function createUser(userData) {
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || `HTTP ошибка! Статус: ${response.status}`);
}

return await response.json();
} catch (error) {
console.error('Ошибка при создании пользователя:', error);
throw error;
}
}

Мария Сорокина, фронтенд-разработчик

Недавно я оптимизировала поисковую систему для каталога продуктов с более чем 50,000 товаров. Изначально поиск работал с задержкой: каждое нажатие клавиши вызывало новый запрос к API, что приводило к "гонке запросов" и непредсказуемым результатам.

Проблема решилась элегантно с помощью AbortController из Fetch API. Вот что я реализовала:

  1. При каждом вводе символа создавался новый AbortController
  2. Предыдущий запрос автоматически отменялся
  3. Добавила задержку 300мс перед отправкой нового запроса
JS
Скопировать код
let controller;

function searchProducts(query) {
// Отменяем предыдущий запрос
if (controller) {
controller.abort();
}

// Создаём новый контроллер
controller = new AbortController();

return fetch(`/api/search?q=${query}`, {
signal: controller.signal
})
.then(response => response.json());
}

// Использование с debounce
const debouncedSearch = debounce(searchProducts, 300);

searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value)
.then(renderResults)
.catch(err => {
if (err.name !== 'AbortError') {
console.error(err);
}
});
});

Результат: снижение нагрузки на сервер на 87%, устранение визуальных "прыжков" интерфейса и повышение точности поиска. Пользователи перестали жаловаться на странное поведение поисковых результатов. Это решение мы впоследствии стандартизировали для всех асинхронных операций в приложении.

При работе с различными типами запросов важно учитывать особенности HTTP-методов:

  • GET — не должен содержать body, параметры передаются в URL
  • POST — отправляет данные на сервер для создания ресурса
  • PUT — заменяет существующий ресурс новым
  • PATCH — частично обновляет существующий ресурс
  • DELETE — удаляет ресурс на сервере

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

После получения ответа от сервера, Fetch API предоставляет разнообразные возможности для обработки и преобразования данных. Объект Response, возвращаемый промисом fetch, содержит всю информацию об ответе и методы для извлечения данных.

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

  • response.ok — булево значение, true если статус ответа в диапазоне 200-299
  • response.status — числовой код статуса HTTP
  • response.statusText — текстовое сообщение статуса
  • response.headers — объект Headers с заголовками ответа
  • response.url — URL ответа
  • response.type — тип ответа (basic, cors, error и т.д.)
  • response.redirected — был ли ответ результатом перенаправления
  • response.body — ReadableStream с телом ответа

Прежде всего, при работе с Fetch API следует проверять успешность запроса:

JS
Скопировать код
fetch('https://api.example.com/data')
.then(response => {
// Проверка успешности запроса
if (!response.ok) {
// Извлекаем информацию об ошибке из тела ответа
return response.json().then(errorData => {
throw new Error(`API вернул ошибку: ${errorData.message || response.status}`);
});
}
return response.json();
})
.then(data => console.log('Данные:', data))
.catch(error => console.error('Ошибка:', error));

Преобразование ответа в разные форматы в зависимости от типа данных:

JS
Скопировать код
// Получение JSON
fetch('/api/users')
.then(response => response.json())
.then(users => console.log(users));

// Получение текста (например, HTML или XML)
fetch('/api/document')
.then(response => response.text())
.then(document => console.log(document));

// Получение бинарных данных (например, изображения)
fetch('/api/image.png')
.then(response => response.blob())
.then(imageBlob => {
const imageUrl = URL.createObjectURL(imageBlob);
const imgElement = document.createElement('img');
imgElement.src = imageUrl;
document.body.appendChild(imgElement);
});

// Получение FormData
fetch('/api/form-data')
.then(response => response.formData())
.then(formData => {
for (const [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
});

// Получение ArrayBuffer
fetch('/api/binary-data')
.then(response => response.arrayBuffer())
.then(buffer => {
// Работа с ArrayBuffer
const view = new Uint8Array(buffer);
console.log(view);
});

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

JS
Скопировать код
fetch('https://api.example.com/data')
.then(response => {
// Получение отдельного заголовка
const contentType = response.headers.get('Content-Type');
console.log('Тип контента:', contentType);

// Перебор всех заголовков
console.log('Все заголовки:');
for (const [key, value] of response.headers.entries()) {
console.log(`${key}: ${value}`);
}

// Проверка наличия заголовка
if (response.headers.has('X-Rate-Limit')) {
console.log('Ограничение запросов:', response.headers.get('X-Rate-Limit'));
}

return response.json();
});

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

Тип ошибки Описание Как обрабатывать
Ошибки сети Отсутствие соединения, CORS, недоступный сервер Перехват в catch
HTTP-ошибки (4xx, 5xx) Неверные параметры, ошибки сервера Проверка response.ok
Ошибки парсинга Некорректный формат данных (невалидный JSON) try/catch вокруг методов преобразования
AbortError Запрос был прерван Проверка error.name === 'AbortError'
Таймауты Слишком долгое ожидание ответа Комбинация Promise.race и setTimeout

Пример комплексной обработки ошибок:

JS
Скопировать код
async function fetchWithErrorHandling(url) {
try {
// Создаём таймаут для запроса
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Таймаут запроса')), 5000);
});

// Используем Promise.race для ограничения времени ожидания
const response = await Promise.race([
fetch(url),
timeoutPromise
]);

// Проверяем HTTP-статус
if (!response.ok) {
let errorMessage;

try {
// Пробуем извлечь сообщение об ошибке из тела ответа
const errorData = await response.json();
errorMessage = errorData.message || `Ошибка HTTP: ${response.status}`;
} catch (e) {
// Если не удалось распарсить JSON, используем статус
errorMessage = `Ошибка HTTP: ${response.status} ${response.statusText}`;
}

throw new Error(errorMessage);
}

// Пытаемся распарсить JSON
try {
return await response.json();
} catch (e) {
throw new Error('Невалидный формат JSON в ответе');
}
} catch (error) {
// Классифицируем ошибки
if (error.name === 'AbortError') {
console.error('Запрос был прерван');
} else if (error.message === 'Таймаут запроса') {
console.error('Сервер не ответил вовремя');
} else if (error.message.includes('NetworkError') || error.message.includes('Failed to fetch')) {
console.error('Проблема с сетевым соединением');
} else {
console.error('Ошибка API:', error.message);
}

// Пробрасываем ошибку дальше
throw error;
}
}

Продвинутые техники: загрузка файлов и прерывание запросов

Fetch API предоставляет мощные инструменты для реализации сложных сценариев взаимодействия с сервером. Рассмотрим продвинутые техники, которые повысят эффективность вашего кода. 🚀

Загрузка файлов на сервер с помощью FormData:

JS
Скопировать код
// Загрузка одного файла
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);

try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});

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

return await response.json();
} catch (error) {
console.error('Проблема при загрузке файла:', error);
throw error;
}
}

// Использование с input[type="file"]
document.querySelector('#fileInput').addEventListener('change', async (event) => {
const file = event.target.files[0];
if (file) {
try {
const result = await uploadFile(file);
console.log('Файл успешно загружен:', result);
} catch (error) {
console.error('Ошибка:', error);
}
}
});

Загрузка множества файлов с отслеживанием прогресса:

JS
Скопировать код
async function uploadFilesWithProgress(files, progressCallback) {
const formData = new FormData();

// Добавляем все файлы в FormData
Array.from(files).forEach((file, index) => {
formData.append(`file${index}`, file);
});

// XMLHttpRequest используется для отслеживания прогресса
// (Fetch API пока не имеет встроенной поддержки отслеживания прогресса)
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();

xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
progressCallback(percentComplete.toFixed(2));
}
});

xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const response = JSON.parse(xhr.responseText);
resolve(response);
} catch (e) {
resolve(xhr.responseText);
}
} else {
reject(new Error(`HTTP ошибка: ${xhr.status}`));
}
});

xhr.addEventListener('error', () => reject(new Error('Сетевая ошибка')));
xhr.addEventListener('abort', () => reject(new Error('Загрузка прервана')));

xhr.open('POST', '/api/upload-multiple');
xhr.send(formData);
});
}

// Использование
document.querySelector('#multiFileInput').addEventListener('change', async (event) => {
const files = event.target.files;
if (files.length > 0) {
try {
const result = await uploadFilesWithProgress(files, (percent) => {
document.querySelector('#progressBar').value = percent;
document.querySelector('#progressText').textContent = `${percent}%`;
});
console.log('Файлы успешно загружены:', result);
} catch (error) {
console.error('Ошибка:', error);
}
}
});

Прерывание запросов с помощью AbortController:

JS
Скопировать код
// Создаём функцию для прерываемого запроса
function fetchWithTimeout(url, options = {}, timeoutMs = 5000) {
// Создаём контроллер прерывания
const controller = new AbortController();
const { signal } = controller;

// Создаём таймер, который прервёт запрос по истечении timeoutMs
const timeout = setTimeout(() => controller.abort(), timeoutMs);

// Добавляем signal к опциям запроса
return fetch(url, { ...options, signal })
.then(response => {
// Очищаем таймер при успешном ответе
clearTimeout(timeout);
return response;
})
.catch(error => {
// Очищаем таймер при ошибке
clearTimeout(timeout);
// Проверяем, была ли ошибка вызвана превышением времени ожидания
if (error.name === 'AbortError') {
throw new Error(`Запрос превысил время ожидания (${timeoutMs}ms)`);
}
throw error;
});
}

// Пример использования с возможностью ручного прерывания
let activeController = null;

function startSearch(query) {
// Прерываем предыдущий поиск, если он активен
if (activeController) {
activeController.abort();
}

// Создаём новый контроллер
activeController = new AbortController();

// Индикатор загрузки
document.querySelector('#searchStatus').textContent = 'Поиск...';

fetch(`/api/search?q=${query}`, {
signal: activeController.signal
})
.then(response => response.json())
.then(results => {
document.querySelector('#searchStatus').textContent = 'Поиск завершен';
displayResults(results);
})
.catch(error => {
if (error.name === 'AbortError') {
console.log('Поиск был прерван');
} else {
document.querySelector('#searchStatus').textContent = 'Ошибка поиска';
console.error('Ошибка поиска:', error);
}
})
.finally(() => {
if (activeController.signal.aborted) {
activeController = null;
}
});
}

// Кнопка прерывания поиска
document.querySelector('#cancelSearch').addEventListener('click', () => {
if (activeController) {
activeController.abort();
document.querySelector('#searchStatus').textContent = 'Поиск отменен';
}
});

Параллельные запросы с помощью Promise.all и Promise.allSettled:

JS
Скопировать код
// Выполнение нескольких запросов параллельно
async function fetchMultipleData() {
try {
const [users, products, settings] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/products').then(r => r.json()),
fetch('/api/settings').then(r => r.json())
]);

return { users, products, settings };
} catch (error) {
// Если любой из запросов завершится с ошибкой, выполнение прервётся
console.error('Один из запросов завершился с ошибкой:', error);
throw error;
}
}

// Параллельные запросы с независимой обработкой ошибок
async function fetchMultipleDataSafely() {
const results = await Promise.allSettled([
fetch('/api/users').then(r => r.json()),
fetch('/api/products').then(r => r.json()),
fetch('/api/settings').then(r => r.json())
]);

// Обработка результатов с учетом статуса каждого промиса
return results.map((result, index) => {
const endpointNames = ['users', 'products', 'settings'];
if (result.status === 'fulfilled') {
return { name: endpointNames[index], data: result.value, success: true };
} else {
console.error(`Ошибка при загрузке ${endpointNames[index]}:`, result.reason);
return { name: endpointNames[index], error: result.reason, success: false };
}
});
}

Повторные попытки запросов при временных ошибках:

JS
Скопировать код
async function fetchWithRetry(url, options = {}, maxRetries = 3, delayMs = 1000) {
let lastError;

for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
// Увеличиваем задержку с каждой попыткой (экспоненциальная задержка)
if (attempt > 0) {
const backoffDelay = delayMs * Math.pow(2, attempt – 1);
console.log(`Повторная попытка ${attempt} через ${backoffDelay}ms...`);
await new Promise(resolve => setTimeout(resolve, backoffDelay));
}

const response = await fetch(url, options);

if (!response.ok) {
// Для определенных HTTP-статусов повторяем запрос
if ([429, 500, 502, 503, 504].includes(response.status)) {
const error = new Error(`HTTP ошибка ${response.status}`);
error.status = response.status;
throw error;
}

// Для других ошибок прекращаем попытки
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP ошибка ${response.status}`);
}

return response;
} catch (error) {
lastError = error;

// Не повторяем попытки для определенных ошибок
if (
(error.name === 'AbortError') ||
(error.status && ![429, 500, 502, 503, 504].includes(error.status))
) {
throw error;
}

// Если это последняя попытка, пробрасываем ошибку
if (attempt === maxRetries – 1) {
console.error(`Достигнуто максимальное количество попыток (${maxRetries})`);
throw lastError;
}
}
}
}

Fetch API кардинально изменил подход к асинхронным запросам в веб-разработке. Промисы и возможность использования async/await сделали код более читаемым и управляемым. Не бойтесь переходить на современные стандарты — они не просто следуют моде, но и решают реальные проблемы разработчиков. Независимо от того, создаёте ли вы простое приложение или сложную систему, Fetch API предлагает элегантное решение для всех типов HTTP-взаимодействий. Даже с появлением более новых библиотек и инструментов, понимание фундаментальных принципов Fetch API остаётся необходимым навыком, который позволяет гибко и эффективно работать с серверными данными в любых условиях.

Читайте также

Проверь как ты усвоил материалы статьи
Пройди тест и узнай насколько ты лучше других читателей
Какой главный недостаток Fetch API по сравнению с XMLHttpRequest?
1 / 5

Тимур Голубев

веб-разработчик

Свежие материалы

Загрузка...