Объект XMLHttpRequest
Код javascript может взаимодействовать с каким-нибудь ресурсом в сети интернет, например с сервером. Для взаимодействия кода javascript с сервером обычно применяется такая технология как Ajax. Ajax представляет технологию для отправки запросов к серверу из клиентского кода JavaScript без перезагрузки страницы. Сам термин расшифровывается как Asynchronous JavaScript And XML. То есть изначально AJAX предполагал асинхронное взаимодействие клиента и сервера посредством данных в формате XML. Хотя сейчас XML во многом вытеснил формат JSON, в любом случае AJAX революционизировал веб-среду, позволив создавать динамичные отзывчивые веб-приложения.
Для создания приложений использующих Ajax, применяются различные способы. Но самым распространенным способом является использование объекта XMLHttpRequest:
let xhr = new XMLHttpRequest();
После создания объекта XMLHttpRequest можно отправлять запросы к серверу, для начала надо вызвать метод open() для инициализации:
xhr.open(method, url, async, user, password)
Эта функция принимает пять параметров, из которых первые два являются обязательными:
methodтип запросаGET,POST,PUT,DELETEи т.д.urlадрес ресурса, к которому отправляется запросasyncлогическое значение которое указывает будет ли запрос асинхронным. Если значениеtrue(значение по умолчанию), то запрос асинхронный. Синхронный и асинхронный режим отличаются тем, что запрос в синхронном режиме пока запрос не выполнится, остальной код javascript не может выполняться. Если запрос отправляется в асинхронном режиме, то параллельно с выполнением запроса можно выполнять также и другой код javascript. И в большинстве случаев, как правило, используется именно асинхронный режимuserимя пользователя которое применяется при его аутентификации на сервере (то есть для определения, какой именно пользователь осуществил запрос), по умолчанию равноnullpasswordпароль пользователя, который применяется при его аутентификации на сервере, по умолчанию равноnull
Например, запрос типа GET на файл исполнитель xmlhttprequest.php:
xhr.open("GET", "xmlhttprequest.php");
После инициализации запроса методом open() можно отправить запрос с помощью метода send(). В body находится тело запроса. Не у всякого запроса есть тело, например у GET тела нет, у POST основные данные как раз передаются через body:
xhr.send(body)
Вызов метода abort() прерывает выполнение запроса:
xhr.abort()
Ответы XMLHttpRequest
Объект XMLHttpRequest имеет ряд свойств, которые позволяют проконтролировать выполнение запроса:
statusсодержит статусный код ответаHTTP, который пришел от сервера. С помощью статусного кода можно судить об успешности запроса или об ошибках, которые могли бы возникнуть при его выполнении. Например, статусный код200указывает на то, что запрос прошел успешно. Код403говорит о необходимости авторизации для выполнения запроса, а код404сообщает, что ресурс не найден и так далееstatusTextТекстовое описание статуса от сервераOK,Not Found,Forbiddenи так далееresponseTypeвозвращает тип ответа, есть следующие типы:
""пустая строка
arraybufferответ представляет объектArrayBuffer, которые содержит бинарные данные
blobответ представляет объектBlob, которые содержит бинарные данные
documentответ представляет документHTML/XML
jsonответ представляет данные в форматеjson
textответ представляет текстresponseвозвращает ответ сервераresponseTextвозвращает ответа сервера в виде текстаresponseXMLвозвращаетXML/HTML, если ответ от сервера в форматеXML/HTML
Синхронные и асинхронные запросы
Если в методе open установить параметр async равным false, то запрос будет синхронным.
Синхронные вызовы используются чрезвычайно редко, так как блокируют взаимодействие со страницей до окончания загрузки. Посетитель не может даже прокручивать её. Никакой JavaScript не может быть выполнен, пока синхронный вызов не завершён — в общем, в точности те же ограничения как alert.
Если синхронный вызов занял слишком много времени, то браузер предложит закрыть «зависшую» страницу.
Из-за такой блокировки получается, что нельзя отослать два запроса одновременно. Кроме того, забегая вперёд, заметим, что ряд продвинутых возможностей, таких как возможность делать запросы на другой домен и указывать таймаут, в синхронном режиме не работают.
Из всего вышесказанного уже должно быть понятно, что синхронные запросы используются чрезвычайно редко, а асинхронные — почти всегда.
Для того, чтобы запрос стал асинхронным, укажем параметр async равным true.
let xhr = new XMLHttpRequest();
xhr.open("GET", "xmlhttprequest.php", true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState != 4) return;
if (xhr.status != 200) {
// обработать ошибку
alert(xhr.status + ": " + xhr.statusText);
} else {
// вывести результат
alert(xhr.responseText);
}
};
Если в open указан третий аргумент true (или если третьего аргумента нет), то запрос выполняется асинхронно. Это означает, что после вызова xhr.send() код не «зависает», а преспокойно продолжает выполняться, а результат приходит через событие xhr.onreadystatechange.
Событие readystatechange
Событие readystatechange происходит несколько раз в процессе отсылки и получения ответа. При этом можно посмотреть «текущее состояние запроса» в свойстве xhr.readyState.
В примере выше мы использовали только состояние 4 (запрос завершён), но есть и другие.
Все состояния, по спецификации:
const unsigned short UNSENT = 0; // начальное состояние
const unsigned short OPENED = 1; // вызван open
const unsigned short HEADERS_RECEIVED = 2; // получены заголовки
const unsigned short LOADING = 3; // загружается тело (получен очередной пакет данных)
const unsigned short DONE = 4; // запрос завершён
Запрос проходит их в порядке 0 → 1 → 2 → 3 → … → 3 → 4, состояние 3 повторяется при каждом получении очередного пакета данных по сети.
HTTP-заголовки
XMLHttpRequest умеет как указывать свои заголовки в запросе, так и читать присланные в ответ. Для работы с HTTP-заголовками есть 3 метода:
setRequestHeader
setRequestHeader(name, value), устанавливает заголовок name запроса со значением value:
xhr.setRequestHeader('Content-Type', 'application/json');
Нельзя установить заголовки, которые контролирует браузер, например Referer или Host и ряд других (полный список тут).
Особенностью XMLHttpRequest является то, что отменить setRequestHeader невозможно. Повторные вызовы лишь добавляют информацию к заголовку, например:
xhr.setRequestHeader('X-Auth', '123');
xhr.setRequestHeader('X-Auth', '456');
// в результате будет заголовок:
// X-Auth: 123, 456
getResponseHeader
getResponseHeader(name), возвращает значение заголовка ответа name, кроме Set-Cookie и Set-Cookie2:
xhr.getResponseHeader('Content-Type');
getAllResponseHeaders
getAllResponseHeaders(), возвращает все заголовки ответа, кроме Set-Cookie и Set-Cookie2. Заголовки возвращаются в виде единой строки. Между заголовками стоит перевод строки в два символа "\r\n" (не зависит от ОС), значение заголовка отделено двоеточием с пробелом ": ". Этот формат задан стандартом. Таким образом, если хочется получить объект с парами заголовок-значение, то эту строку необходимо разбить и обработать:
Cache-Control: max-age=31536000
Content-Length: 4260
Content-Type: image/png
Date: Sat, 08 Sep 2012 16:53:16 GMT
Таймаут
Максимальную продолжительность асинхронного запроса можно задать свойством timeout:
xhr.timeout = 30000; // 30 секунд (в миллисекундах)
При превышении этого времени запрос будет оборван и сгенерировано событие ontimeout:
xhr.ontimeout = function() {
alert( 'Извините, запрос превысил максимальное время' );
}
Полный список событий
Современная спецификация предусматривает следующие события по ходу обработки запроса:
loadstartзапрос начатprogressбраузер получил очередной пакет данных, можно прочитать текущие полученные данные вresponseTextabortзапрос был отменён вызовомxhr.abort()errorпроизошла ошибкаloadзапрос был успешно (без ошибок) завершёнtimeoutзапрос был прекращён по таймаутуloadendзапрос был завершён (успешно или неуспешно)
Используя эти события можно более удобно отслеживать загрузку (onload) и ошибку (onerror), а также количество загруженных данных (onprogress).
Ранее мы видели ещё одно событие — readystatechange. Оно появилось гораздо раньше, ещё до появления текущего стандарта.
Пример использования асинхронно
function loadPhones() {
let xhr = new XMLHttpRequest();
xhr.open("GET", "xmlhttprequest.php", true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState != 4) return;
if (xhr.status != 200) {
// обработать ошибку
alert(xhr.status + ": " + xhr.statusText);
} else {
// вывести результат
alert(xhr.responseText);
}
};
}
Пример использования синхронно
function loadPhones() {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'xmlhttprequest.php', false);
xhr.send();
if (xhr.status != 200) {
// обработать ошибку
alert('Ошибка ' + xhr.status + ': ' + xhr.statusText);
} else {
// вывести результат
alert(xhr.responseText);
}
}