Ajax

AJAX

Что это такое

Ajax — это техника, которая позволяет обмениваться данными с сервером из уже открытой страницы и не перезагружать её целиком. Изначально название расшифровывалось как Asynchronous JavaScript and XML, однако XML давно уступил место JSON, а механизм запроса перешёл от XMLHttpRequest к API fetch. Сама идея при этом не изменилась: обновить из сети часть страницы, а всё остальное оставить на месте.

Почему это важно

Пока Ajax не было, каждое взаимодействие означало отправку формы и загрузку страницы: заметное мерцание, потерянная позиция прокрутки, поход на сервер ради одной галочки. Именно Ajax заставляет приложение ощущаться приложением, а не документом, который перезагружается. Вместе с тем он вводит класс проблем, которого у перезагружаемой страницы нет, — частичное состояние. Страница может находиться посреди запроса, запрос может тихо отказать, а два запроса — обогнать друг друга. Поэтому понимать саму технику важнее, чем помнить имя API.

Ключевые идеи

Страница обновляется, а не заменяется

Запрос получает данные, а код обновляет только ту часть дерева, которая изменилась; остальное он не трогает. В этом и выгода — не нужно заново рендерить шапку, позиция прокрутки сохраняется, — и в этом же источник сложности: DOM и данные сервера теперь могут расходиться.

async function refreshTotals() {
  const response = await fetch("/api/cart/totals");
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const totals = await response.json();
  document.querySelector("[data-total]").textContent = formatMoney(totals.total);
}

fetch завершается успешно, даже когда запрос не удался

Промис отклоняется при сетевом сбое, а вот ответ 404 или 500 завершается успешно, со ok, установленным в false. Принять завершившийся ответ за успех — самая частая ошибка в Ajax: HTML страницы с ошибкой разбирается как JSON, и сбой всплывает как сбивающая с толку ошибка разбора.

Просите JSON и проверяйте форму

Промис возвращает response.json(), ведь тело читается после прихода заголовков. Разобранное значение имеет тип any, поэтому ожидаемая форма так и остаётся предположением, пока её что-нибудь не проверит. Проверяйте на границе — для всего, что сложнее прототипа.

const data = await response.json();
if (typeof data?.total !== "number") throw new Error("unexpected payload");

Отправляйте данные правильным методом и типом

Заголовок Content-Type: application/json обязателен для POST с телом JSON; отправка формы должна использовать FormData без заголовка, чтобы границу задал браузер. Ошибка здесь даёт запрос, который сервер не может разобрать, и обычно это выглядит как пустое тело.

Деградируйте, когда скрипт не запускается

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

Практика

Загрузите и отрисуйте список без перезагрузки

Сделайте кнопку, которая получает список, отрисовывает его на странице, а при сбое и пока запрос в полёте показывает отдельное сообщение.

Готово, когда: кнопка показывает отключённое состояние загрузки, на ответ 500 выводится сообщение об ошибке, а не пустой список, и при этом остальная часть страницы не перерисовывается.

Дайте форме рабочий запасной вариант

Возьмите форму, которая отправляется через Ajax, и убедитесь, что она работает с отключённым JavaScript, а затем проверьте, что улучшенный путь даёт тот же результат.

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

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

  • Предположение, что завершившийся fetch означает успех — тело ответа 404 разбирается как ожидаемая полезная нагрузка; исправить: проверяйте response.ok и бросайте исключение со статусом.
  • Отрисовка до завершения запроса — состояние загрузки подменяется пустым, и интерфейс выглядит сломанным; исправить: отрисовывайте три явных состояния: загрузка, готово и ошибка.
  • Два запроса, которые обгоняют друг друга — медленный ранний ответ перезаписывает быстрый поздний; исправить: прерывайте предыдущий запрос или игнорируйте ответ, чей запрос уже не актуален.
  • Забытый тип содержимого JSON при записи — сервер получает неразбираемое тело; исправить: задайте Content-Type: application/json и сериализуйте через JSON.stringify.
  • Страница, которая работает только с Ajax — заблокированный или упавший скрипт не оставляет ничего рабочего; исправить: начните с работающей ссылки или формы и улучшайте её.

Что читать дальше