Работа с сетью: технология AJAX

Раздел: Сетевое взаимодействие -> Асинхронные запросы

Основные методы асинхронных запросов

Для взаимодействия с сервером без перезагрузки страницы в JavaScript применяется AJAX. Наиболее современный и удобный подход - Fetch API. Он основан на промисах и предоставляет лаконичный синтаксис.

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

Ajax js (ajax в javascript)

Метод fetch принимает URL и возвращает промис. Первый вызов .then преобразует ответ в JSON, второй - обрабатывает данные. Блок .catch перехватывает сетевые ошибки.

Типичные проблемы:

  • Ошибка CORS - сервер не разрешает запрос с другого домена. Решение: настройка сервера или использование прокси.
  • Ответ не в формате JSON - вызов response.json() выбросит исключение. Следует проверять response.ok.
  • Отсутствие обработки статусов HTTP - промис fetch не отклоняется при 404 или 500. Необходимо проверять response.status.

Как выполнить запрос с помощью XMLHttpRequest?

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

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onload = function() {
  if (xhr.status === 200) {
    const data = JSON.parse(xhr.responseText);
    console.log(data);
  } else {
    console.error('Ошибка:', xhr.status);
  }
};
xhr.onerror = function() {
  console.error('Сетевая ошибка');
};
xhr.send();

new XMLHttpRequest() создаёт объект запроса. Параметр true в open означает асинхронный режим. Обработчики onload и onerror реагируют на результат.

Частые ошибки:

  • Забытый вызов send() - запрос не отправится.
  • Использование синхронного режима (false) блокирует интерфейс.
  • Некорректный парсинг JSON через JSON.parse без проверки xhr.responseType.

Как использовать async/await с Fetch?

Синтаксис async/await делает асинхронный код похожим на синхронный, улучшая читаемость. Рекомендуется для сложных цепочек запросов.

async function loadData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) throw new Error('HTTP ' + response.status);
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Ошибка:', error);
  }
}
loadData();

Ключевое слово await приостанавливает выполнение функции до завершения промиса. Блок try...catch обрабатывает исключения.

Возможные сложности:

  • Забытая обработка response.ok - при 404 промис не отклоняется, но данные не получены.
  • Использование await вне асинхронной функции - вызовет синтаксическую ошибку.

Как отправить POST запрос с JSON?

Для отправки данных на сервер используется POST. Fetch позволяет передать объект с опциями: method, headers, body.

const payload = { name: 'Анна', age: 30 };
fetch('https://api.example.com/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload)
})
  .then(response => response.json())
  .then(data => console.log('Успех:', data))
  .catch(error => console.error('Ошибка:', error));

Заголовок Content-Type указывает серверу формат данных. JSON.stringify преобразует объект в строку.

Проблемы:

  • Неправильный заголовок - сервер может не распознать данные.
  • Отсутствие JSON.stringify - запрос отправит [object Object].
  • Несоответствие типа данных в ответе сервера.

Как обрабатывать ошибки и таймауты?

Таймаут для fetch не встроен, но реализуется через AbortController. Ошибки сети обрабатываются в .catch или try...catch.

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

fetch('https://api.example.com/data', { signal: controller.signal })
  .then(response => {
    clearTimeout(timeoutId);
    if (!response.ok) throw new Error('HTTP ' + response.status);
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.error('Запрос прерван по таймауту');
    } else {
      console.error('Ошибка:', error);
    }
  });

AbortController создаёт сигнал, который передаётся в fetch. Вызов abort() отклоняет промис с исключением AbortError.

Сложности:

  • Необходимость очищать таймер после успешного ответа, чтобы избежать лишнего abort.
  • Поддержка AbortController - все современные браузеры, для старых требуется полифилл.

Расширенные примеры использования AJAX

1. Отправка формы с использованием FormData

Пример
const form = document.querySelector('form');
form.addEventListener('submit', async (event) => {
  event.preventDefault();
  const formData = new FormData(form);
  try {
    const response = await fetch('/submit', { method: 'POST', body: formData });
    const result = await response.text();
    console.log('Результат:', result);
  } catch (error) {
    console.error('Ошибка отправки формы:', error);
  }
});
// Вывод при успехе: 'Форма отправлена' или 'Спасибо'

FormData автоматически собирает поля формы. Content-Type устанавливается как multipart/form-data.

2. Загрузка файла с индикацией прогресса (XMLHttpRequest)

Пример
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
xhr.upload.onprogress = (event) => {
  if (event.lengthComputable) {
    const percent = (event.loaded / event.total) * 100;
    console.log(`Загружено ${percent}%`);
  }
};
xhr.onload = () => console.log('Файл загружен');
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const formData = new FormData();
formData.append('file', file);
xhr.send(formData);
// Загружено 45%
// Загружено 100%
// Файл загружен

Событие progress на объекте upload позволяет отслеживать ход отправки. Fetch не поддерживает прогресс отправки напрямую.

3. Параллельные запросы с Promise.all

Пример
const urls = [
  'https://api.example.com/users',
  'https://api.example.com/posts'
];
Promise.all(urls.map(url =>
  fetch(url).then(res => {
    if (!res.ok) throw new Error('Ошибка ' + res.status);
    return res.json();
  })
))
.then(([users, posts]) => {
  console.log('Пользователи:', users);
  console.log('Посты:', posts);
})
.catch(error => console.error(error));
// Пользователи: [{id:1, name:'Иван'}, ...]
// Посты: [{id:10, title:'Пример'}, ...]

Promise.all ожидает завершения всех промисов. При отклонении любого из них срабатывает catch.

4. Запрос с кастомными заголовками и авторизацией

Пример
const token = 'my-secret-token';
fetch('https://api.example.com/secure', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'X-Custom-Header': 'custom-value'
  }
})
  .then(response => response.json())
  .then(data => console.log(data));
// Данные защищённого ресурса

Кастомные заголовки передаются в объекте headers. Обратите внимание на CORS - сервер должен разрешать эти заголовки.

5. Отмена запроса при помощи AbortController (полный пример)

Пример
const controller = new AbortController();
const signal = controller.signal;

// Эмуляция медленного запроса
const fetchPromise = fetch('https://httpbin.org/delay/5', { signal });

// Отмена через 2 секунды
setTimeout(() => controller.abort(), 2000);

fetchPromise
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Запрос отменён по таймауту');
    } else {
      console.error('Ошибка:', err);
    }
  });
// Запрос отменён по таймауту

AbortController применим не только к fetch, но и к другим асинхронным операциям.

6. Обработка JSONP (для старых кросс-доменных запросов)

Пример
function jsonp(url, callbackName) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = `${url}?callback=${callbackName}`;
    window[callbackName] = (data) => {
      resolve(data);
      document.body.removeChild(script);
      delete window[callbackName];
    };
    script.onerror = () => reject(new Error('JSONP error'));
    document.body.appendChild(script);
  });
}

jsonp('https://api.example.com/data', 'myCallback')
  .then(data => console.log(data))
  .catch(error => console.error(error));
// { result: 'jsonp data' }

JSONP использует тег script для обхода CORS. Сервер должен поддерживать callback. Современные API чаще используют CORS, поэтому JSONP считается устаревшим.

7. Использование async iterators для потоковой обработки (fetch + ReadableStream)

Пример
async function* streamResponse(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    yield decoder.decode(value, { stream: true });
  }
}

(async () => {
  for await (let chunk of streamResponse('https://api.example.com/stream')) {
    console.log('Получен фрагмент:', chunk);
  }
})();
// Получен фрагмент: '{"id":1}'
// Получен фрагмент: ',{"id":2}'
// ...

Потоковое чтение полезно для больших ответов или событийных каналов (SSE).

AJAX в JavaScript - comments

En
Ajax js (javascript)