Отправить запрос без перезагрузки страницы

Устали от мигающих экранов? Учим отправку запроса без перезагрузки страницы за 5 минут. Превратите сайт в плавное приложение!

Бесплатная консультация юриста

+7 (800) 302-87-29

Реклама jurik.ru

Современная веб-разработка немыслима без технологии AJAX (Asynchronous JavaScript and XML). Этот термин объединяет подходы, позволяющие веб-приложениям обмениваться данными с сервером асинхронно, без полной перезагрузки страницы. Это обеспечивает плавный пользовательский интерфейс, экономит трафик и ускоряет взаимодействие.

В этой статье мы подробно разберем эволюцию инструментов: от устаревшего XMLHttpRequest к современному стандарту Fetch API, а также рассмотрим практические примеры отправки форм, работы с JSON и обработки ошибок.


Что такое AJAX и зачем это нужно?

AJAX — это не отдельный язык программирования или библиотека, а набор технологий, работающих вместе:

  • DOM — для динамического отображения и взаимодействия с данными.
  • XMLHttpRequest (XHR) или Fetch API — для асинхронного обмена данными с сервером.

Ключевые преимущества:

  1. Обновление частей страницы: Можно обновить только корзину товаров, список комментариев или профиль пользователя, не перезагружая весь документ.
  2. Асинхронность: Пользователь продолжает взаимодействовать с интерфейсом, пока идет запрос к серверу.

История: 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 ошибки.

Fetch НЕ отклоняет промис при HTTP-ошибках (4xx, 5xx). Промис разрешается успешно (в состояние fulfilled) с объектом Response, у которого 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. Настройте её на сервере.

Лучшие практики и чек-лист

  1. Всегда используйте async/await. Код становится линейным, читаемым и простым для отладки;
  2. Проверяйте response.ok. Никогда не доверяйте только блоку try/catch для HTTP-ошибок.
  3. Используйте FormData для форм. Это стандарт де-факто для multipart/form-data.
  4. Не забывайте про Content-Type: application/json при отправке JSON вручную (Fetch не ставит его автоматически для объектов).
  5. Обрабатывайте состояние загрузки (Loading). Блокируйте кнопки, показывайте спиннеры, скелетоны контента.
  6. Используйте AbortController для отмены устаревших запросов (поиск, пагинация).
  7. Типизируйте ответы (TypeScript). Описывайте интерфейсы для response.json.
  8. Централизуйте логику. Создайте обертку (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 без полифиллов).