Fetch API для AJAX запросов: полное руководство с примерами кода
#Web API #Асинхронность #Fetch APIДля кого эта статья:
- JavaScript разработчики, стремящиеся улучшить свои навыки в работе с сетевыми запросами
- Изучающие современные технологии для веб-программирования
- Программисты, использующие или желающие перейти с XMLHttpRequest на Fetch API
JavaScript разработка долгое время страдала от неуклюжего XMLHttpRequest — того самого динозавра, с которым мы мучились годами для выполнения AJAX-запросов. Но наступила эра Fetch API — элегантного и мощного инструмента, который позволяет работать с сетевыми запросами так, как будто вы пишете стихи, а не код. Если вы всё ещё используете старые способы взаимодействия с API, эта статья станет вашим спасательным кругом в океане современного веб-программирования. Готовы навсегда изменить способ работы с сетевыми запросами? 🚀
Что такое Fetch API и почему она заменяет AJAX запросы
Fetch API — это современный интерфейс JavaScript для выполнения HTTP-запросов, который предоставляет более мощную и гибкую альтернативу старому доброму XMLHttpRequest (XHR). В основе Fetch лежит концепция Promise, что делает асинхронные операции значительно более читаемыми и поддерживаемыми.
Прежде чем мы погрузимся в технические детали, давайте разберемся, почему Fetch API постепенно вытесняет традиционные AJAX-запросы:
- Современный синтаксис — более чистый, элегантный код с использованием Promise
- Модульность — разделение запроса и ответа на отдельные объекты
- Нативная поддержка — встроена в современные браузеры без необходимости дополнительных библиотек
- Потоковая обработка данных — возможность работать с данными по мере их поступления
- Упрощенная обработка JSON — встроенные методы для работы с JSON-ответами
| Характеристика | XMLHttpRequest | Fetch API |
|---|---|---|
| Синтаксис | Сложный, с множеством обратных вызовов | Элегантный, основанный на Promise |
| Обработка ошибок | Требует дополнительных проверок | Унифицированный через цепочку .catch() |
| Поддержка JSON | Ручной парсинг через JSON.parse() | Встроенный метод .json() |
| Отмена запроса | Встроенная функция xhr.abort() | Через AbortController (более новые браузеры) |
| Поддержка в браузерах | Все браузеры | Все современные браузеры (IE требует полифил) |
Антон Кравцов, Lead Frontend Developer
Когда я присоединился к проекту по редизайну платформы онлайн-образования, код был заполнен jQuery AJAX-запросами. Это была настоящая головная боль — разобраться в многочисленных вложенных функциях обратного вызова и "адском треугольнике" из скобок. Первое, что я сделал — перевёл все запросы на Fetch API.
Результат превзошёл ожидания: код стал более плоским, читаемым и понятным для новых членов команды. Обработка ошибок упростилась благодаря цепочкам промисов. Интеграция с нашим API стала настолько прозрачной, что мы смогли быстрее выпускать новые функции. Самое забавное — размер бандла уменьшился на 15%, просто потому что мы избавились от jQuery, который использовался в основном для AJAX.

Основной синтаксис Fetch API и создание простых запросов
Основное очарование Fetch API заключается в его простоте и выразительности. Давайте разберёмся с базовым синтаксисом и рассмотрим, как создавать различные типы запросов.
Базовый синтаксис Fetch выглядит так:
fetch(url, options)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Ошибка:', error));
Здесь url — адрес ресурса, к которому мы обращаемся, а options — необязательный объект с настройками запроса. Давайте рассмотрим основные типы запросов.
GET-запрос — самый простой тип запроса, используемый для получения данных:
// Простой GET-запрос
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Ошибка при получении данных:', error));
POST-запрос — используется для отправки данных на сервер:
// POST-запрос с JSON-данными
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Пользователь',
email: 'user@example.com'
})
})
.then(response => response.json())
.then(data => console.log('Успешный ответ:', data))
.catch(error => console.error('Ошибка:', error));
Другие типы запросов (PUT, DELETE и т.д.) создаются аналогичным образом, просто изменяя значение параметра method. Вот полный список основных HTTP-методов, которые вы можете использовать с Fetch API:
- GET — получение данных (используется по умолчанию)
- POST — отправка данных для создания нового ресурса
- PUT — обновление существующего ресурса полностью
- PATCH — частичное обновление ресурса
- DELETE — удаление ресурса
- OPTIONS — получение информации о поддерживаемых методах
- HEAD — получение только заголовков ответа без тела
Async/await синтаксис делает работу с Fetch API еще более элегантной и читаемой:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Произошла ошибка:', error);
}
}
fetchData();
Этот синтаксис особенно полезен при выполнении нескольких последовательных запросов или при сложной логике обработки данных. 🔄
Обработка ответов и преобразование данных в Fetch API
Одно из ключевых преимуществ Fetch API — элегантная работа с ответами сервера. Когда метод fetch() выполняется, он возвращает Promise, который разрешается в объект Response, представляющий HTTP-ответ.
Объект Response предоставляет множество полезных методов для обработки полученных данных:
- response.json() — преобразует ответ в JSON-объект
- response.text() — получает ответ в виде текста
- response.blob() — работа с бинарными данными (изображения, файлы)
- response.formData() — получает ответ в виде объекта FormData
- response.arrayBuffer() — получает низкоуровневое представление данных
Важно понимать, что эти методы также возвращают Promise, поэтому требуется дополнительная обработка через .then() или await.
Рассмотрим пример обработки JSON-ответа:
fetch('https://api.example.com/users')
.then(response => {
// Проверка успешности ответа
if (!response.ok) {
throw new Error(`Ошибка HTTP: ${response.status}`);
}
return response.json(); // Преобразование в JSON
})
.then(data => {
// Работа с JSON-данными
console.log('Получены пользователи:', data);
// Дальнейшая обработка данных...
})
.catch(error => console.error('Ошибка получения или обработки данных:', error));
Для работы с бинарными данными, например, при загрузке изображения:
fetch('https://example.com/image.jpg')
.then(response => response.blob())
.then(blob => {
const imageUrl = URL.createObjectURL(blob);
const imgElement = document.createElement('img');
imgElement.src = imageUrl;
document.body.appendChild(imgElement);
})
.catch(error => console.error('Ошибка при загрузке изображения:', error));
Важно отметить при работе с Response — проверка статуса ответа. Fetch API не отклоняет Promise даже при получении HTTP-ошибки (например, 404 или 500). Promise будет отклонён только при сетевой ошибке. Поэтому необходимо явно проверять свойство response.ok или response.status:
fetch('https://api.example.com/nonexistent')
.then(response => {
if (!response.ok) {
throw new Error(`Ошибка HTTP: ${response.status}`);
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Проблема с запросом:', error));
Мария Соколова, Frontend Team Lead
В одном из проектов нам требовалось реализовать функционал загрузки и отображения PDF-документов. Изначально я попыталась использовать старый подход с XMLHttpRequest, настраивая тип ответа как 'arraybuffer', но код получался неуклюжим и трудноподдерживаемым.
Решение пришло, когда я переписала логику на Fetch API. Мы загружали PDF как Blob и создавали URL-объект для отображения. Самое приятное открытие — мы смогли добавить индикатор прогресса загрузки, используя Response.body в качестве ReadableStream. Это было невозможно с XMLHttpRequest без дополнительных хаков.
Пользователи сразу заметили разницу: документы начали отображаться постепенно, по мере загрузки, а не ждали полной загрузки файла. Особенно это оценили клиенты с медленным интернетом. Такие моменты заставляют ценить современные API браузера!
Продвинутые техники работы с Fetch: заголовки и опции
Для серьезной работы с API необходимо уметь настраивать запросы, добавлять заголовки и изменять различные параметры. Fetch API предоставляет богатый набор опций для тонкой настройки HTTP-запросов. 🔧
Рассмотрим основные опции, которые можно передать в метод fetch():
| Параметр | Описание | Возможные значения |
|---|---|---|
| method | HTTP-метод запроса | 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', и т.д. |
| headers | Объект с HTTP-заголовками | {'Content-Type': 'application/json', ...} |
| body | Тело запроса (для POST/PUT/PATCH) | JSON.stringify(data), FormData, Blob, и т.д. |
| mode | CORS режим | 'cors', 'no-cors', 'same-origin' |
| credentials | Отправка куки и авторизации | 'include', 'same-origin', 'omit' |
| cache | Управление кэшированием | 'default', 'no-cache', 'reload', 'force-cache', 'only-if-cached' |
| redirect | Обработка перенаправлений | 'follow', 'error', 'manual' |
| referrer | Заголовок Referrer | URL или пустая строка |
| signal | Объект AbortSignal для отмены запроса | AbortController.signal |
Работа с заголовками
Заголовки — критически важная часть HTTP-запросов. Они позволяют передавать метаданные, токены авторизации и другую служебную информацию.
// Запрос с авторизационными заголовками
fetch('https://api.example.com/protected-data', {
headers: {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXV...',
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Custom-Header': 'CustomValue'
}
})
.then(response => response.json())
.then(data => console.log(data));
Вы также можете получить заголовки из ответа:
fetch('https://api.example.com/data')
.then(response => {
// Получаем отдельный заголовок
console.log('Content-Type:', response.headers.get('Content-Type'));
// Перебираем все заголовки
response.headers.forEach((value, name) => {
console.log(`${name}: ${value}`);
});
return response.json();
});
Отправка различных типов данных
Fetch API позволяет отправлять разные форматы данных. Вот несколько примеров:
- Отправка JSON-данных:
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Иван Петров',
email: 'ivan@example.com'
})
});
- Отправка данных формы:
const formData = new FormData();
formData.append('username', 'user123');
formData.append('avatar', fileInput.files[0]);
fetch('https://api.example.com/profile', {
method: 'POST',
body: formData
// Не указывайте Content-Type при использовании FormData,
// браузер установит его автоматически с правильной boundary
});
- Отправка URL-encoded данных (как из обычной HTML-формы):
fetch('https://api.example.com/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'username=user123&password=securepass'
});
Кросс-доменные запросы и CORS
При работе с разными доменами важно понимать CORS (Cross-Origin Resource Sharing). Вы можете настроить режим CORS с помощью опции mode:
fetch('https://other-domain.com/api/data', {
mode: 'cors', // Явно указываем режим CORS
credentials: 'include' // Отправлять куки при кросс-доменных запросах
});
- cors — стандартный режим, требующий поддержки CORS на сервере
- no-cors — ограниченный режим для запросов к серверам без поддержки CORS (нельзя прочитать содержимое ответа)
- same-origin — запрещает кросс-доменные запросы
Параметр credentials определяет, будут ли отправляться куки и данные авторизации при запросе:
- omit — не отправлять куки
- same-origin — отправлять только при запросах на тот же домен
- include — отправлять куки даже при кросс-доменных запросах (требует специальной настройки на сервере)
Обработка ошибок и отмена запросов в Fetch API
Грамотная обработка ошибок и возможность отмены запросов — важные аспекты при работе с сетевыми операциями. Fetch API предлагает элегантные решения для этих задач.
Комплексная обработка ошибок
Как уже упоминалось, Fetch не отклоняет Promise при получении HTTP-ошибки (404, 500 и т.д.), а только при сетевых проблемах. Рекомендуется использовать следующую структуру для надежной обработки всех типов ошибок:
function fetchWithErrorHandling(url, options = {}) {
return fetch(url, options)
.then(response => {
if (!response.ok) {
// Создаём расширенный объект ошибки с дополнительной информацией
const error = new Error(`HTTP error! Status: ${response.status}`);
error.status = response.status;
error.response = response;
throw error; // Прокидываем ошибку в catch
}
return response.json();
})
.catch(error => {
// Здесь обрабатываем как сетевые ошибки, так и HTTP-ошибки
if (error.status) {
console.error(`Ошибка HTTP ${error.status}: ${error.message}`);
// Специфическая обработка различных HTTP-статусов
switch (error.status) {
case 401:
console.log('Необходимо авторизоваться');
// Перенаправляем на страницу логина
break;
case 403:
console.log('Доступ запрещён');
break;
case 404:
console.log('Ресурс не найден');
break;
case 500:
console.log('Ошибка сервера');
break;
}
} else {
// Сетевая ошибка или другая проблема
console.error('Сетевая ошибка:', error.message);
}
// Пробрасываем ошибку дальше или возвращаем fallback-данные
throw error; // или return defaultData;
});
}
// Использование
fetchWithErrorHandling('https://api.example.com/data')
.then(data => console.log('Данные получены:', data))
.catch(error => {
// Сюда попадут только ошибки, которые не обработаны в fetchWithErrorHandling
console.error('Критическая ошибка:', error);
});
Отмена запросов с помощью AbortController
Современные браузеры поддерживают AbortController API, который позволяет отменять запросы Fetch:
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(error => {
if (error.name === 'AbortError') {
console.log('Запрос был отменён пользователем');
} else {
console.error('Другая ошибка:', error);
}
});
// Где-то в другом месте кода (например, при нажатии кнопки "Отмена")
// controller.abort();
// Пример с таймаутом
setTimeout(() => {
controller.abort(); // Отмена запроса через 5 секунд
console.log('Запрос отменён из-за таймаута');
}, 5000);
Реализация таймаута для запросов
Fetch API не имеет встроенной поддержки таймаутов, но их можно реализовать с помощью AbortController и Promise.race():
function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const { signal } = controller;
// Создаём Promise, который отклонится через timeout миллисекунд
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
controller.abort();
reject(new Error(`Запрос превысил таймаут в ${timeout}мс`));
}, timeout);
});
// Запускаем гонку между fetch и таймаутом
return Promise.race([
fetch(url, { ...options, signal }),
timeoutPromise
]);
}
// Использование
fetchWithTimeout('https://api.example.com/data', {}, 3000)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Ошибка или таймаут:', error.message));
Повторные попытки при сбоях
При работе с нестабильными API полезно реализовать механизм повторных попыток:
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let error;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
return await response.json();
} catch (err) {
error = err;
console.log(`Попытка ${i + 1} неудачна, ${maxRetries – i – 1} осталось`);
// Увеличиваем время ожидания между попытками (экспоненциальная задержка)
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
// Если все попытки неудачны, выбрасываем последнюю ошибку
throw error;
}
Грамотная обработка ошибок и управление жизненным циклом запросов — это то, что отличает профессиональный код от любительского. Применение этих техник делает ваши приложения более устойчивыми к сбоям и улучшает пользовательский опыт. 🛡️
Fetch API — это не просто замена устаревшему XMLHttpRequest, а настоящий прорыв в области сетевых взаимодействий JavaScript. Она предлагает более чистый, логичный и мощный подход к работе с HTTP-запросами. Обладая интеграцией с современными концепциями JavaScript, такими как Promise и async/await, Fetch API позволяет писать более поддерживаемый и понятный код. Даже если вы только начинаете освоение этой технологии, польза от перехода на Fetch API будет очевидна с первых строк кода. Помните, что хороший разработчик не просто использует инструменты, а понимает, почему и как они работают. Дополните ваш арсенал этим мощным API и поднимите качество вашего кода на новый уровень.
Читайте также
Тимур Голубев
веб-разработчик
