Современная веб-разработка немыслима без технологии AJAX (Asynchronous JavaScript and XML). Этот термин объединяет подходы, позволяющие веб-приложениям обмениваться данными с сервером асинхронно, без полной перезагрузки страницы. Это обеспечивает плавный пользовательский интерфейс, экономит трафик и ускоряет взаимодействие.
В этой статье мы подробно разберем эволюцию инструментов: от устаревшего XMLHttpRequest к современному стандарту Fetch API, а также рассмотрим практические примеры отправки форм, работы с JSON и обработки ошибок.
Что такое AJAX и зачем это нужно?
AJAX — это не отдельный язык программирования или библиотека, а набор технологий, работающих вместе:
- DOM — для динамического отображения и взаимодействия с данными.
- XMLHttpRequest (XHR) или Fetch API — для асинхронного обмена данными с сервером.
Ключевые преимущества:
- Обновление частей страницы: Можно обновить только корзину товаров, список комментариев или профиль пользователя, не перезагружая весь документ.
- Асинхронность: Пользователь продолжает взаимодействовать с интерфейсом, пока идет запрос к серверу.
История: XMLHttpRequest (XHR), «Старый надежный»
До появления Fetch API основным инструментом был объект XMLHttpRequest. Он все еще работает во всех браузерах и встречается в легаси-коде.
Пример классического XHR (POST запрос)
function sendDataXHR {
const xhr = new XMLHttpRequest;
const url = '/api/user';
const data = JSON.stringify({ name: 'Иван', age: 30 });
xhr.open('POST', url, true); // true = асинхронно
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
xhr.onload = function {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('Успех:', JSON.parse(xhr.responseText));
} else {
console.error('Ошибка сервера:', xhr.status, xhr.statusText);
}
};
xhr.onerror = function {
console.error('Сетевая ошибка');
};
xhr.send(data);
}
Недостатки XHR: громоздкий синтаксис, коллбек-ад (callback hell) при цепочке запросов, отсутствие нативной поддержки промисов (Promise).
Современный стандарт: Fetch API
Fetch API — это нативный браузерный интерфейс, основанный на Promise (промисах). Он предоставляет более чистый, модульный и мощный способ выполнения сетевых запросов. Метод fetch принимает один обязательный аргумент — путь к ресурсу (URL), и возвращает промис, который разрешается в объект Response.
Базовый синтаксис
fetch(url, options)
.then(response => { /* обработка ответа / })
.catch(error => { / обработка сетевых ошибок */ });
Параметр options (объект инициализации)
Второй аргумент позволяет контролировать запрос:
- method: HTTP-метод (
GET,POST,PUT,DELETEи т.д.). По умолчаниюGET. - headers: Объект заголовков (например,
Content-Type). - body: Тело запроса (строка,
FormData,Blob,JSON.stringify(obj)). Не используется дляGET/HEAD. - mode: Режим CORS (
cors,no-cors,same-origin). - credentials: Политика куков (
omit,same-origin,include). - cache: Режим кэширования.
- redirect: Политика редиректов (
follow,error,manual). - signal:
AbortSignalдля отмены запроса.
Практические примеры использования Fetch
GET запрос (Получение данных)
Самый простой сценарий — получить JSON с сервера.
async function fetchUsers {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
// Важно: fetch не считает HTTP-ошибки (404, 500) отклонением промиса!
if (!response.ok) {
throw new Error(`HTTP ошибка: ${response.status}`);
}
const users = await response.json;
console.log(users);
renderUsers(users); // Ваша функция отрисовки
} catch (error) {
console.error('Ошибка загрузки:', error);
}
}
POST запрос (Отправка JSON)
Типичная задача — отправить данные формы или действия пользователя.
async function createPost(, body) {
const url = 'https://jsonplaceholder.typicode.com/posts';
const data = { , body, userId: 1 };
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`Статус: ${response.status}`);
const newPost = await response.json;
console.log('Создано:', newPost);
return newPost;
} catch (error) {
console.error('Ошибка отправки:', error);
}}
Отправка формы (FormData) — Универсальное решение
Современный подход: перехват сабмита формы, сбор данных через FormData и отправка через fetch. Это работает для любых форм на сайте без привязки к конкретным полям.
document.addEventListener('DOMContentLoaded', => {
// Находим все формы с атрибутом data-ajax="true"
const forms = document.querySelectorAll('form[data-ajax="true"]');
forms.forEach(form => {
form.addEventListener('submit', async (e) => {
e.preventDefault; // 1. Отменяем стандартную перезагрузку
const submitBtn = form.querySelector('[type="submit"]');
const originalBtnText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.textContent = 'Отправка...';
try {
// 2. Собираем данные автоматически (включая файлы!)
const formData = new FormData(form);
// Можно добавить доп. поля: formData.append('ajax', 'true');
// 3. Отправляем. Content-Type (multipart/form-data)
// установится АВТОМАТИЧЕСКИ с правильной boundary-строкой.
const response = await fetch(form.action || window.location.href, {
method: form.method || 'POST',
body: formData
});
if (!response.ok) throw new Error(`Ошибка сервера: ${response.status}`);
const result = await response.json; // Ожидаем JSON ответ
if (result.success) {
alert('Данные успешно сохранены!');
form.reset; // Очистка формы
// Например, закрытие модалки или редирект:
// window.location.href = result.redirectUrl;
} else {
// Ошибки валидации с сервера
showErrors(result.errors);
}
} catch (error) {
console.error(error);
alert('Произошла техническая ошибка. Попробуйте позже.');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = originalBtnText;
}
}); });
});
Почему FormData лучше ручного сбора?
- Автоматически обрабатывает
input[type="file"]. - Кодирует данные в
multipart/form-data(необходимо для файлов). - Работает с
checkbox,radio,select multipleкорректно из коробки. - Не требует обновления JS-кода при изменении полей формы.
Работа с объектом Response
Объект Response содержит полезные свойства и методы для чтения тела ответа:
| Метод / Свойство | Описание |
|---|---|
response.ok | Boolean. True, если статус 200-299. Главный флаг успеха. |
response.status | Код состояния HTTP (200, 404, 500...). |
response.statusText | Текст статуса (например, "OK", "Not Found"). |
response.headers | Объект Headers для чтения заголовков ответа. |
response.json | Возвращает промис, разрешающийся в JSON-объект. |
response.text | |
response.blob | Для скачивания файлов, изображений (создает Blob). |
response.formData | Парсит ответ как FormData (редко используеться). |
response.clone | Создает копию ответа. Тело можно прочитать только один раз! |
Важно: Тело ответа (Body) — это поток (ReadableStream). Его можно прочитать только один раз. Если нужно прочитать дважды (например, логировать и парсить), используйте response.clone.
Обработка ошибок: Нюансы Fetch
Самая частая ошибка новичков: думать, что catch ловит 404 или 500 ошибки.
ok: false. Промис отклоняется (rejected) только при сетевых сбоях: нет интернета, DNS ошибка, CORS блокировка, отмена запроса (AbortController).
Правильный паттерн обработки ошибок
async function safeFetch(url) {
try {
const response = await fetch(url);
// 1. Проверяем HTTP статус вручную
if (!response.ok) {
// Можно прочитать тело ошибки, если сервер прислал JSON с деталями
let errorData = {};
try { errorData = await response.json; } catch (e) { /* игнор */ }
// Бросаем свою ошибку с данными
const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
error.response = response;
error.data = errorData;
throw error;
}
// 2. Парсим успешный ответ
return await response.json;
} catch (error) {
// 3. Ловим И сетевые ошибки, И наши ручные throw
if (error instanceof TypeError && error.message.includes('Failed to fetch')) {
console.error('Сетевая ошибка / CORS / Offline');
// Показать уведомление "Проверьте интернет"
} else {
console.error('Ошибка приложения:', error.message);
// Показать error.data пользователю (валидация и т.д.)
}
throw error; // Пробрасываем дальше, если нужно
}
}
Продвинутые темы
Отмена запроса (AbortController)
Полезно для автокомплита (поиск по мере ввода): отменяем предыдущий запрос, если пользователь продолжил печатать.
const controller = new AbortController;
const signal = controller.signal;
// Через 5 секунд отменяем, если сервер не ответил
const timeoutId = setTimeout( => controller.abort, 5000);
fetch('/api/slow-endpoint', { signal })
.then(response => response.json)
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('Запрос отменен по таймауту или пользователем');
} else {
console.error('Другая ошибка:', err);
}
}) .finally( => clearTimeout(timeoutId));
Загрузка файлов с прогресс-баром
Fetch API нативно не поддерживает отслеживание прогресса загрузки (upload progress). Для этой задачи до сих пор приходится использовать XMLHttpRequest или библиотеки (axios), которые оборачивают XHR.
function uploadWithProgress(file, url, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest;
xhr.open('POST', url);
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
onProgress(percent);
}
};
xhr.onload = => {
if (xhr.status >= 200 && xhr.status < 300) resolve(JSON.parse(xhr.response));
else reject(new Error(xhr.statusText));
};
xhr.onerror = => reject(new Error('Network Error'));
const formData = new FormData;
formData.append('file', file);
xhr.send(formData);
});
}
Безопасность: CSRF и CORS
- CORS (Cross-Origin Resource Sharing): Браузер блокирует запросы на другой домен, если сервер не вернет заголовки
Access-Control-Allow-Origin. Fetch следует этой политике строго. - CSRF (Cross-Site Request Forgery): При отправке форм (особенно с куками
credentials: 'include') обязательно используйте CSRF-токены (в заголовкеX-CSRF-Tokenили скрытом поле формы). - Content Security Policy (CSP): Директива
connect-srcконтролирует, куда может уходить fetch. Настройте её на сервере.
Лучшие практики и чек-лист
- Всегда используйте
async/await. Код становится линейным, читаемым и простым для отладки; - Проверяйте
response.ok. Никогда не доверяйте только блокуtry/catchдля HTTP-ошибок. - Используйте
FormDataдля форм. Это стандарт де-факто дляmultipart/form-data. - Не забывайте про
Content-Type: application/jsonпри отправке JSON вручную (Fetch не ставит его автоматически для объектов). - Обрабатывайте состояние загрузки (Loading). Блокируйте кнопки, показывайте спиннеры, скелетоны контента.
- Используйте
AbortControllerдля отмены устаревших запросов (поиск, пагинация). - Типизируйте ответы (TypeScript). Описывайте интерфейсы для
response.json. - Централизуйте логику. Создайте обертку (wrapper) над fetch для базового URL, заголовков авторизации, единой обработки 401 (рефреш токена), логирования.
Технология отправки запросов без перезагрузки страницы эволюционировала от сложного XMLHttpRequest к элегантному и мощному Fetch API. Современный подход основан на промисах (async/await), нативной поддержке FormData для файлов и форм, и гибкой системе опций запроса.
Освоив Fetch API и нюансы работы с объектом Response (в частности, проверку response.ok), вы сможете создавать быстрые, отзывчивые и удобные веб-интерфейсы уровня Single Page Application (SPA) или прогрессивного улучшения классических сайтов (MPA).
Ключевой вывод: Используйте fetch для 99% задач. Обращайтесь к XMLHttpRequest только если нужен прогресс загрузки файлов (xhr.upload.onprogress) или поддержка очень старых сред (IE11 без полифиллов).