XMLHttpRequest
Изучите XMLHttpRequest (XHR) в JavaScript: методы open и send, readyState, события load/error/timeout, разбор JSON, отправка POST-запросов и отмена запросов.
JavaScript — незаменимый язык программирования для веб-разработки, обеспечивающий динамичное и интерактивное взаимодействие с пользователем. Одна из ключевых возможностей JavaScript — способность обмениваться данными с серверами и обновлять страницы асинхронно. В первую очередь это достигается с помощью XMLHttpRequest (XHR). В этой статье подробно рассматривается XMLHttpRequest: его методы, свойства и практическое применение с множеством примеров кода для лучшего понимания.
XMLHttpRequest работает с функциями обратного вызова. В новом коде вы, как правило, будете использовать Fetch API — он основан на промисах и хорошо сочетается с async/await. XHR всё равно стоит знать: он встречается в старых кодовых базах, и это единственный встроенный API, позволяющий отслеживать детальный прогресс загрузки на стороне отправки.
На этой странице рассматривается, что такое объект XHR, как настроить и отправить запрос с помощью open и send, как читать ответ через readyState и события load/error/timeout, как разбирать JSON, как отправлять данные методом POST, как отменить запрос и чем XHR отличается от fetch.
Общее представление об XMLHttpRequest
XMLHttpRequest (XHR) — встроенный объект браузера, позволяющий JavaScript отправлять HTTP или HTTPS запросы на сервер и получать ответы без перезагрузки страницы. Слово «XML» в названии — дань истории: XHR умеет передавать любые текстовые и бинарные форматы, а JSON сегодня является наиболее распространённым. Возможность обращаться к серверу в фоновом режиме лежит в основе того, что раньше называли AJAX (Asynchronous JavaScript and XML).
Жизненный цикл запроса всегда одинаков: создать объект, открыть его (указать метод и URL), присоединить обработчики событий для реакции на результат, а затем отправить запрос.
Создание объекта XMLHttpRequest
Сначала создайте экземпляр:
const xhr = new XMLHttpRequest();Один экземпляр XMLHttpRequest обрабатывает один запрос. Для второго запроса создайте новый объект.
Отправка HTTP-запроса
После создания объекта настройте его с помощью open, а затем отправьте с помощью send.
Метод open
open инициализирует запрос, но не отправляет его. Метод принимает несколько параметров:
xhr.open(method, url, async, user, password);method: HTTP-метод, например'GET'или'POST'.url: URL, на который отправляется запрос.async: boolean, указывающий, является ли запрос асинхронным. По умолчаниюtrue, и почти всегда стоит оставлять это значение (см. предупреждение ниже).user: необязательное имя пользователя для HTTP-аутентификации.password: необязательный пароль для HTTP-аутентификации.
Пример:
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);Синхронные запросы (xhr.open(method, url, false)) блокируют страницу до получения ответа и устарели в основном потоке. Всегда оставляйте async равным true.
Метод send
send отправляет запрос на сервер. Все обработчики событий должны быть присоединены до его вызова. Для GET-запроса вызывайте без аргументов. Для POST передавайте тело запроса в качестве аргумента.
Пример GET-запроса:
xhr.send();Пример POST-запроса с данными в формате form-encoded:
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('param1=value1¶m2=value2');Метод setRequestHeader добавляет HTTP-заголовок к исходящему запросу и должен вызываться после open, но до send.
Обработка ответов сервера
Для обработки ответов сервера можно использовать различные слушатели событий.
Событие onreadystatechange
Событие onreadystatechange срабатывает при каждом изменении свойства readyState. Свойство readyState хранит статус XMLHttpRequest.
0: UNSENT1: OPENED2: HEADERS_RECEIVED3: LOADING4: DONE
Запрос считается завершённым и успешным только когда readyState равен 4 (DONE) и HTTP status находится в диапазоне успешных ответов (обычно 200). Проверять только readyState === 4 — распространённая ошибка, поскольку сервер мог ответить 404 или 500.
Пример:
Хотя onreadystatechange работает, современный код предпочитает onload и onerror как более простой и читаемый способ обработки запросов. onreadystatechange используется главным образом тогда, когда нужно отслеживать промежуточные состояния (например, прогресс загрузки или получение заголовков).
Событие load
Событие load срабатывает, как только ответ полностью получен. Оно проще, чем onreadystatechange, так как не требует проверки readyState самостоятельно — оно срабатывает только на стадии DONE. Тем не менее status по-прежнему нужно проверять, чтобы отличить реальный успех от HTTP-ошибки.
Пример:
Событие progress
Для больших загрузок можно отображать прогресс с помощью события progress. Когда сервер отправляет заголовок Content-Length, событие является детерминированным (lengthComputable равно true) и можно вычислить процент загрузки:
xhr.onprogress = function(event) {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
console.log(`Downloaded ${percent}%`);
}
};Чтобы отслеживать загрузку на сервер (upload), прикрепите обработчики к xhr.upload (xhr.upload.onprogress). Прогресс upload — единственная возможность, которую Fetch до сих пор не умеет полностью воспроизвести.
Обработка ошибок
Надёжный код должен обрабатывать сбои. Два события охватывают случаи неудачи:
onerrorсрабатывает при сетевой ошибке: запрос не достиг сервера, произошёл сбой DNS, CORS заблокировал запрос и т.д. Обратите внимание, что HTTP-ответ404или500— не сетевая ошибка: он вызываетload, а неerror, поэтомуstatusвсё равно нужно проверять.ontimeoutсрабатывает, если запрос выполняется дольше, чемxhr.timeoutмиллисекунд. Значение0(по умолчанию) означает отсутствие ограничения.
Пример:
Разбор JSON-ответов
Ответы сервера чаще всего имеют формат JSON. Простейший подход — прочитать исходный текст из xhr.responseText и разобрать его самостоятельно с помощью JSON.parse:
Альтернативный вариант — установить xhr.responseType = 'json' перед отправкой, и браузер разберёт тело ответа самостоятельно. Разобранное значение будет доступно в xhr.response (не xhr.responseText):
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);
xhr.responseType = 'json';
xhr.onload = function() {
if (xhr.status === 200) {
console.log('title: ' + xhr.response.title); // already an object
}
};
xhr.send();responseType также принимает значения 'text', 'blob', 'arraybuffer' и 'document' для данных не в формате JSON.
Отправка данных с помощью POST
Чтобы отправить тело в формате JSON, установите заголовок Content-Type и преобразуйте объект с помощью JSON.stringify:
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://jsonplaceholder.typicode.com/posts', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status === 201) { // 201 Created
console.log('Created:', xhr.responseText);
}
};
xhr.send(JSON.stringify({ title: 'foo', body: 'bar', userId: 1 }));Для традиционной отправки формы передайте объект FormData — браузер автоматически установит правильный multipart Content-Type, поэтому вызывать setRequestHeader для него не нужно.
Отмена запроса
Вызовите xhr.abort(), чтобы отменить выполняющийся запрос, например когда пользователь переходит на другую страницу или вводит новый поисковый запрос. После отмены вместо load срабатывает событие abort:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts', true);
xhr.onabort = () => console.log('Request was cancelled');
xhr.send();
// Later, cancel it:
xhr.abort();Эквивалентом в Fetch является AbortController.
XMLHttpRequest против Fetch
| XMLHttpRequest | Fetch | |
|---|---|---|
| Модель программирования | Коллбэки / события | Промисы, работает с await |
| Прогресс загрузки на сервер | Да (xhr.upload) | Нет |
| Прогресс загрузки с сервера | Да (событие progress) | Через потоки (больше кода) |
| Отмена | xhr.abort() | AbortController |
| Ошибка при HTTP-ошибке | Нет, проверяется status | Нет, проверяется response.ok |
Для большинства нового кода предпочтительнее использовать Fetch. К XHR стоит обращаться, когда нужен детальный контроль прогресса загрузки на сервер или необходима поддержка очень старых окружений.
Заключение
XMLHttpRequest позволяет JavaScript обмениваться данными с сервером в фоновом режиме: вы создаёте объект, вызываете open, присоединяете обработчики load/error/timeout и вызываете send. Не забывайте проверять как readyState, так и status, разбирать JSON самостоятельно или через responseType, а также использовать abort() для отмены устаревших запросов. Для большинства нового кода предпочтительнее использовать основанный на промисах Fetch API, однако понимание XHR позволит вам уверенно работать со старыми кодовыми базами и сценариями, где он по-прежнему выигрывает, — например, при отслеживании прогресса загрузки на сервер.