Источник: http://learn.javascript.ru/ajax-xmlhttprequest
Объект XMLHttpRequest (или, как его кратко называют, «XHR») дает возможность браузеру делать HTTP-запросы к серверу без перезагрузки страницы.
Несмотря на слово XML в названии, XMLHttpRequest может работать с данными в любом текстовом формате, и даже c бинарными данными.
Рассмотрим пример с кнопкой для голосования.

Внутри он состоит из трёх частей:
<button onclick="vote(this)">Голосовать</button>
Функция голосования:
function vote(outputElem) {
var xhr = new XMLHttpRequest(); // (1)
xhr.open('GET', '/files/tutorial/ajax/xhr/vote', true); // (2)
xhr.onreadystatechange = function() { // (3)
if (xhr.readyState != 4) return; // (3.1)
outputElem.innerHTML = xhr.responseText; // (3.2)
}
outputElem.innerHTML = '...';
xhr.send(null); // (4)
}
res.end('Ваш голос принят: ' + new Date());
Разберём функцию голосования по шагам:
XMLHttpRequest. Этот объект предназначен для отправки запроса на сервер.xhr конфигурируется: нам нужен GET-запрос на vote. На этом этапе соединение с сервером ещё не открыто.xhr.onreadystatechange задаёт, что делать при ответе сервера.xhr.readyState. Нас интересует только 4 (запрос завершён).xhr.responseText содержит текст ответа сервера.xhr.send(null).Далее мы посмотрим методы и события более подробно.
Эти три метода управляют основным потоком запроса:
open(method, URL, async, user, password)
Задаёт основные параметры запроса:
ftp:// и file://. При этом есть ограничения безопасности, называемые «Same Origin Policy»: запрос со страницы можно отправлять только на тот же протокол://домен:порт, с которого она пришла.send().send(body)
Отправить запрос на сервер.
В body находится тело запроса. Не у всякого запроса есть тело, например у GET-запросов тела нет, в таком случае передаётся null или пустая строка.
С другой стороны, в POST основные данные как раз передаются через body.
abort()
Прерывает выполнение запроса.
Синхронный вызов XMLHttpRequest происходит, если параметр async равен false. В этом случае страница «подвисает»: скрипт ждёт ответа сервера, а затем продолжается — и ответ сервера уже можно использовать:
function voteSync(outputElem) {
var xhr = new XMLHttpRequest(); // (1)
xhr.open('GET', '/files/tutorial/ajax/xhr/vote', false);
outputElem.innerHTML = '...';
xhr.send(null); // (2)
outputElem.innerHTML = xhr.responseText; // (3)
}
При синхронном запросе скрипт останавливается, и страница «подвисает», пока сервер не ответит.
Если запрос — слишком долгий, то большинство браузеров предложат посетителю «убить» процесс с «зависшим» скриптом.
Многие продвинутые возможности XMLHttpRequest, которые мы обсудим в следующих главах, не работают в синхронном режиме.
В частности, не работают кросс-доменные запросы и нельзя указать таймаут.
Всё это делает синхронный XMLHttpRequest редким и нежелательным гостем в веб-приложениях. Далее мы будем использовать только асинхронные запросы.
Выше мы посмотрели код, иллюстрирующий простейшее использование onreadystatechange и responseText. Теперь — время подробнее ознакомиться с свойствами и событиями этого объекта.
Событие readystatechange происходит несколько раз в процессе отсылки и получения ответа. При этом можно посмотреть «текущее состояние запроса» в свойстве xhr.readyState, которое принимает значения от 0 до 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; // запрос завершён
Надёжно и кросс-браузерно работает только последнее состояние: 4 (запрос завершён).
Типичная проверка конца запроса:
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return; // запрос ещё не завершён
// .. обработать завершение запроса, проверить ошибки
}
Остальные состояния:
onreadystatechange, как только получен достаточно большой пакет данных.Пример ниже демонстрирует переключение между состояниями. В нём сервер отвечает на запрос, пересылая по цифре в секунду:
var log = new LogDiv('state-log');
log.log('начали...');
var xhr = new XMLHttpRequest();
xhr.open('GET', '/files/tutorial/ajax/xhr/digits', true);
xhr.onreadystatechange = function() {
log.log("readyState: " + this.readyState + ' responseText: ' + this.responseText);
};
xhr.send('');
Эти свойства содержат HTTP-статус ответа и его описание, например:
| status | statusText |
|---|---|
| 200 | OK |
| 404 | Not Found |
| 500 | Internal Server Error |
| ... | ... |
Когда ошибка не связана с кодом ответа сервера (например, не удалось соединение), свойство status равно нулю, а в statusText — пустая строка.
Хорошей практикой является обязательная проверка на ошибку сравнением status!=200. Например, в функции голосования:
function vote(outputElem) {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/files/tutorial/ajax/xhr/vote', true);
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.status != 200) {
// status=0 при ошибках сети, иначе status=HTTP-код ошибки
alert('Ошибка ' + xhr.status + ': ' + xhr.statusText;
return;
}
// обработать результат
outputElem.innerHTML = xhr.responseText;
}
outputElem.innerHTML = '...';
xhr.send(null);
}
После завершения запроса становится доступно свойство responseText, которое содержит текст ответа сервера. В современных браузерах оно доступно даже при неоконченном запросе и содержит текст, полученный к текущему моменту.
Если сервер прислал HTML/XML с Content-Type: text/xml, то браузер превращает его в полноценный документ и записывает в responseXML. По такому документу можно производить XPath-запросы, делать XSLT-преобразования и т.п.
Например:
var xhr = new XMLHttpRequest();
xhr.open('GET', '/files/tutorial/ajax/xhr/xml', true);
xhr.onreadystatechange = function() {
if (this.readyState != 4) return;
alert(this.responseText);
// responseXML содержит полноценный XML-документ
var author = this.responseXML.getElementsByTagName('author')[0];
alert(author.innerHTML); // undefined: в XML свойство innerHTML не работает!
alert(author.firstChild.data); // "Айн Рэнд", получили через DOM
}
xhr.send('');
Код на сервере:
res.writeHead(200, {'Content-Type': 'text/xml'});
res.end('<book><author>Айн Рэнд</author><title>Атла́нт расправил плечи</title></book>');
Content-Type важен для responseXML.
Самое важное здесь — заголовок Content-Type: text/xml.
Если его нет, то браузер не станет обрабатывать ответ как XML, и свойство responseXML будет пустым.
Для работы с заголовками есть 3 метода:
setRequestHeader(name, value)
Устанавливает заголовок name запроса со значением value. Если заголовок с таким name уже есть — он заменяется.
Например:
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
Нельзя установить заголовки, которые контролирует браузер, например Referer или Host и ряд других (полный список тут).
Это ограничение существует в целях безопасности и для контроля корректности запроса.
Особенностью XMLHttpRequest является то, что отменить setRequestHeader невозможно.
Повторные вызовы добавляют информацию к заголовку:
xhr.setRequestHeader('X-Auth', '123');
xhr.setRequestHeader('X-Auth', '456');
// в результате будет заголовок:
// X-Auth: 123, 456
getResponseHeader(name)
Возвращает значение заголовка ответа name, кроме Set-Cookie и Set-Cookie2.
Например:
xhr.getResponseHeader('Content-Type') == 'text/plain'
getAllResponseHeaders() Возвращает все заголовки ответа, кроме Set-Cookie и Set-Cookie2. Заголовки возвращаются в виде единой строки, например:
Cache-Control: max-age=31536000
Content-Length: 4260
Content-Type: image/png
Date: Sat, 08 Sep 2012 16:53:16 GMT
Между заголовками стоит перевод строки в два символа "\r\n" (не зависит от ОС), значение заголовка отделено двоеточием с пробелом ": ". Этот формат задан стандартом.
Таким образом, если хочется получить объект с парами заголовок-значение, то эту строку необходимо разбить и обработать.
Результат запроса XMLHttpRequest, как и обычная страница, может быть закэширован браузером.
IE<10 автоматически кэширует ответы, не снабжённые антикэш-заголовком.
Это опасно, поскольку может поломать интерфейс. Тем более, что другие браузеры этого не делают. Например, если вы кликните на кнопку «Голосовать», то в IE запрос будет сделан только один раз, повторные клики возьмут результат из кэша.
Чтобы этого избежать, сервер должен добавить в ответ соответствующие антикэш-заголовки, например:
Cache-Control: no-cache
Альтернативный вариант — добавить в URL запроса случайный параметр, предотвращающий кэширование.
Например:
xhr.open('GET', '/xhr/vote?r=' + Math.random(), ...)
Внимание, в XMLHttpRequest сломано кэширование!
Вообще, если механизм кэширования обычных страниц через HTTP-заголовки хорошо проработан, то при кешировании XMLHttpRequest многие заголовки игнорируются или работают некорректно, поэтому полагаться на них не следует.
Это касается Cache-Control, Last-Modified, Expires и других.
А чтобы не быть голословным — вот отличный набор онлайн-тестов, которые проверят поведение вашего браузера: XMLHTTPREQUEST CACHING TESTS.
Если вы хотите использовать браузерные кэширующие заголовки в сочетании с XMLHttpRequest — убедитесь, что они поддерживаются браузерами.
Максимальную продолжительность запроса можно задать свойством timeout:
xhr.timeout = 30000; // 30 секунд (в миллисекундах)
При превышении этого времени запрос будет оборван и сгенерировано событие ontimeout:
xhr.ontimeout = function() {
alert('Извините, запрос превысил максимальное время');
}
responseText.abort().Звёздочкой отмечены события, которые поддерживаются начиная в IE8 объектом XDomainRequest.
Типовой код для запроса XMLHttpRequest:
var xhr = new XMLHttpRequest();
xhr.open('GET', '/my/url', true);
xhr.onreadystatechange = function() {
if (this.readyState != 4) return;
// по окончании запроса доступны:
// status, statusText
// responseText, responseXML (при content-type: text/xml)
if (this.status != 200) {
// обработать ошибку
return;
}
// получить результат из this.responseText или this.responseXML
}
xhr.send('');
Основные методы:
open(method, url, async, user, password)send(body)abort(body)setRequestHeader(name, value)getResponseHeader(name)getAllResponseHeaders()Основные свойства:
responseTextresponseXMLstatusstatusTextКроме того, есть особенности при работе с кэшированием: