Объект XMLHttpRequest

Объект XMLHttpRequest (или, сокращенно, XHR) дает возможность браузеру делать HTTP-запросы к серверу без перезагрузки страницы.

Все современные браузеры (IE7+, Firefox, Chrome, Safari и Opera) имеют встроенный объект XMLHttpRequest.

Синтаксис для создания объекта XMLHttpRequest:
var xhr = new XMLHttpRequest();

В IE8 и IE9 поддержка XMLHttpRequest ограничена, но имеют свой объект XDomainRequest, который реализовывал часть возможностей современного стандарта.

Кросс-браузерно:
var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest;
var xhr = new XHR();

Создаём XMLHttpRequest и проверяем, поддерживает ли он событие onload. Если нет, то это старый XMLHttpRequest, значит это IE8,9, и используем XDomainRequest.

Для IE10+ обычный XMLHttpRequest уже является полноценным.

События

onreadystatechange

Вызывается при изменении значения свойства readyState. Наиболее важен для обработки ситуации завершения запроса.

onreadystatechange как свойство XMLHttpRequest поддерживается во всех браузерах.

xhr.onreadystatechange = обработчик
или
xhr.addEventListener( "readystatechange", обработчик )

обработчик - функция-обработчик события (можно указать анонимную функцию или ссылку на готовую).




onabort

Вызывается при прерывании запроса.

xhr.onabort = обработчик

xhr.addEventListener( "abort", обработчик )



onerror

Вызывается в случае завершения запроса по ошибке. Обратите внимание, что HTTP-коды состояния, такие как 404, не считаются ошибкой, поскольку сам ответ получен успешно. Однако это событие может породить отрицательный ответ сервера DNS или бесконечный цикл переа дресаций.

xhr.onerror = обработчик

xhr.addEventListener( "error", обработчик )



onload

Вызывается при успешном выполнении запроса. В обработчик передается объект XMLHttpRequestProgressEvent

xhr.onload = обработчик

xhr.addEventListener( "load", обработчик )



onloadend

Вызывается в случае успешного или неудачного завершения запроса, после событий «load», «abort», «error» и «timeout».

xhr.onloadend = обработчик

xhr.addEventListener( "loadend", обработчик )



onloadstart

Вызывается с началом выполнения запроса.

xhr.onloadstart = обработчик

xhr.addEventListener( "loadstart", обработчик )



onprogress

Вызывается периодически (примерно раз в 50 миллисекунд) в ходе загрузки тела ответа.

xhr.onprogress = обработчик

xhr.addEventListener( "progress", обработчик )



ontimeout

Вызывается, если истекло время ожидания, определяемое свойством timeout, а ответ так и не был принят.

xhr.ontimeout = обработчик

xhr.addEventListener( "timeout", обработчик )

Свойства

readyState
xhr.readyState 

Состояние HTTP-запроса. В момент создания объекта XMLHttpRequest это свойство приобретает значение 0, а к моменту получения полного HTTP-ответа это значение возрастает до 4.

Значение свойства readyState может уменьшаться, только если в процессе выполнения запроса был вызван метод abort() или open().

Теоретически при каждом изменении значения этого свойства должен вызываться обработчик события onreadystatechange. Однако на практике событие гарантированно возникает, только когда свойство readyState получает значение 4. (События «progress», введенные спецификацией XHR2, обеспечивают более надежный способ слежения за ходом выполнения запроса.)

Все состояния, по спецификации:

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 повторяется при каждом получении очередного пакета данных по сети.




response
xhr.response 

В спецификации XHR2 это свойство хранит ответ сервера. Тип свойства зависит от значения свойства responseType.

Если responseType содержит пустую строку или строку «text», данное свойство содержит тело ответа в виде строки.

Если responseType содержит строку «document», значением данного свойства будет объект Document, полученный в результате разбора XML- или HTML-документа в теле ответа.

Если responseType содержит строку «arraybuffer», значением данного свойства будет объект ArrayBuffer, представляющий двоичные данные в теле ответа.

Если responseType содержит строку «blob», значением данного свойства будет объект Blob, представляющий двоичные данные в теле ответа.

Если responseType содержит строку «json», значением данного свойства будет объект JSON, полученный путем парсинга JSON строки, полученной с сервера.




responseText
xhr.responseText 

Если значение свойства readyState меньше 3, данное свойство будет содержать пустую строку.

Если значение свойства readyState равно 3, данное свойство возвращает часть ответа, которая была принята к текущему моменту.

Если значение свойства readyState равно 4, это свойство содержит полное тело ответа.

Если в ответе имеется заголовок, определяющий кодировку символов в теле ответа, используется эта кодировка, в противном случае предполагается кодировка UTF-8.




responseType
xhr.responseType = [ "text" | "document" | "arraybuffer" |  "blob" | "json" ]

В спецификации XHR2 это свойство определяет тип ответа и тип свойства response. Прежде чем отправить запрос, необходимо для свойства xhr.responseType указать значение «text», «document», «arraybuffer», «blob» или «json». Значением по умолчанию является пустая строка, которая также является синонимом значения «text». Если установить это свойство вручную, последующие попытки обратиться к свойствам responseText и responseXML будут возбуждать исключения и для получения ответа сервера необходимо будет использовать свойство response, предусмотренное спецификацией XHR2.




responseXML
xhr.responseXML 

Ответ на запрос, который интерпретируется как XML- или HTML-документ и возвращается в виде объекта Document. Это свойство будет иметь значение null, если тело ответа еще не получено или оно не является допустимым XML или HTML-документом.




status
xhr.status 

HTTP-код состояния, полученный от сервера, такой как 200 – в случае успеха, 404 – в случае ошибки отсутствия документа или 0 – если сервер еще не прислал код состояния.




statusText
xhr.statusText 

Это свойство содержит текст, соответствующий HTTP-коду состояния в ответе. То есть, когда свойство status имеет значение 200, это свойство содержит строку «OK», а когда 404 – строку «Not Found». Это свойство содержит пустую строку, если сервер еще не прислал код состояния.




timeout
xhr.timeout = time

Свойство, введенное спецификацией XHR2, определяющее предельное время ожидания ответа в миллисекундах. Если выполнение HTTP-запроса займет больше времени, чем указано в данном свойстве, он будет прерван и будет сгенерировано событие «timeout».

Это свойство можно установить только после вызова метода open() и перед вызовом метода send().




upload
xhr.upload 

Свойство, введенное спецификацией XHR2, ссылающееся на объект XMLHttpRequestUpload, который определяет набор свойств регистрации обработчиков событий для слежения за процессом выгрузки тела HTTP-запроса.




withCredentials
xhr.withCredentials [ = true|false ]

Свойство, введенное спецификацией XHR2, определяющее необходимость аутентификации при выполнении междоменного CORS-запроса и необходимость обработки заголовков cookie в CORS-ответах. По умолчанию имеет значение false.

Как указано в спецификации www.w3.org/TR/cors/#omit-credentials-flag, withCredentials позволяет нам использовать в запросе к серверу user-credentials, т.е. cookie, аутентификационные данные и клиентские SSL-сертификаты.

Указание withCredentials=true необходимо не только для отправки «user-credentials» в запросе к серверу, но и для использования их из ответов от сервера.

Методы

abort()
xhr.abort()

Возвращает объект XMLHttpRequest в исходное состояние, соответствующее значению 0 в свойстве readyState, и отменяет любые запланированные сетевые взаимодействия. Этот метод может потребоваться, например, если запрос выполняется слишком долго и надобность в получении ответа уже отпала.




getAllResponseHeaders()
xhr.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" (не зависит от ОС), значение заголовка отделено двоеточием с пробелом ": ". Этот формат задан стандартом.

Таким образом, если хочется получить объект с парами заголовок-значение, то эту строку необходимо разбить и обработать.




getResponseHeader()
xhr.getResponseHeader( header )

Возвращает значение указанного заголовка header в HTTP-ответе или null, если заголовки вообще не были получены или если ответ не содержит требуемого заголовка header. Заголовки cookie и CORS отфильтровываются, и их нет смысла запрашивать. Если было принято несколько заголовков с указанным именем, значения этих заголовков объединяются в одну строку через запятую и пробел. Например:

var header = xhr.getResponseHeader('Content-Type');



open()
xhr.open( method, url[,  async,  user, pass] )

Этот метод инициализирует объект XMLHttpRequest и сохраняет свои аргументы для последующего использования методом send().

Аргумент method определяет HTTP-метод, используемый для отправки запроса. Среди наиболее устоявшихся методов можно назвать GET, POST и HEAD. Реализации могут также поддерживать методы CONNECT, DELETE, OPTIONS, PUT,TRACE и TRACK.

Аргумент url определяет URL-адрес, который является предметом запроса. Разрешение относительных URL-адресов производится обычным образом с использованием URL-адреса документа со сценарием. Политика общего происхождения требует, чтобы данный URL-адрес содержал те же имя хоста и номер порта, что и документ со сценарием, выполняющим запрос. Объект XHR2 позволяет выполнять междоменные запросы к серверам, поддерживающим заголовки CORS. В качестве URL можно использовать не только http/https, но и другие протоколы, например ftp:// и file://.

Если аргумент async указан и имеет значение false, запрос будет выполняться в синхронном режиме, и последующий вызов send() заблокирует работу сценария, пока ответ не будет получен полностью. Синхронные запросы рекомендуется использовать только в фоновых потоках выполнения.

Необязательные аргументы user и pass определяют имя пользователя и пароль для HTTP-запроса.

Метод open() не выполняет запрос. Он его только конфигурирует. Для отправки запроса используется метод send().




overrideMimeType()
xhr.overrideMimeType( mime )

Этот метод позволяет указать, что ответ сервера должен интерпретироваться в соответствии с указанным MIME-типом mime (и параметром charset, если он указан в определении типа mime), без учета значения заголовка Content-Type в ответе.

Другими словами, метод позволяет переопределить тип получаемых с сервера данных. И даже существует специальная кодировка, которая специально была задумана для приема бинарных данных — x-user-defined (XMLHttpRequest автоматически преобразует все принятые данные в юникод). Поэтому простое добавление следующей строчки должно сделать большую часть работы.

xhr.overrideMimeType('text/plain; charset=x-user-defined');



send()
xhr.send( [body] )

Именно этод метод открывает соединение и отправляет запрос на сервер. Если перед этим не вызывался метод open() или, обобщенно, если значение свойства readyState не равно 1, метод send() возбуждает исключение. В противном случае он начинает выполнение HTTP-запроса,

Если в предшествующем вызове метода open() аргумент async имел значение false, данный метод блокируется и не возвращает управление, пока значение свойства readyState не станет равно 4 и ответ сервера не будет получен полностью. В противном случае метод send() немедленно возвращает управление, а ответ сервера обрабатывается асинхронно, с помощью обработчиков событий.

Аргумент body определяет «тело» запроса. Не у всякого запроса есть «тело», например у GET-запросов «тела» нет, а у POST – основные данные как раз передаются через body.




setRequestHeader()
xhr.setRequestHeader( name, value )

Определяет HTTP-заголовок с именем name и значением value, который должен быть включен в запрос, передаваемый последующим вызовом метода send(). Этот метод может вызываться, только когда свойство readyState имеет значение 1, т. е. после вызова метода open(), но перед вызовом метода send().

Если заголовок с именем «name» уже был определен, новым значением заголовка станет прежнее значение заголовка плюс запятая с пробелом и новое значение «value», переданное методу.

Если методу open() была передана информация об авторизации, объект XMLHttpRequest автоматически добавит заголовок Authorization. Однако этот заголовок может быть также добавлен методом setRequestHeader().

Объект XMLHttpRequest автоматически устанавливает заголовки «Content-Length», «Date», «Referer» и «User-Agent» и не позволяет изменять их значения.

POST, FormData

Чтобы сделать POST-запрос, можно использовать встроенный объект FormData.

Конструктор:

let formData = new FormData([form]); // создаём объект, по желанию берём данные формы <form>

Если передать в конструктор элемент HTML-формы form, то создаваемый объект автоматически прочитает из неё поля.

Его особенность заключается в том, что методы для работы с сетью позволяют указать объект FormData в свойстве тела запроса body.

Он будет соответствующим образом закодирован и отправлен с заголовком Content-Type: form/multipart.

То есть, для сервера это выглядит как обычная отправка формы.

Мы создаём объект, при желании указываем, из какой формы form взять данные, затем, если нужно, с помощью метода append добавляем дополнительные поля, после чего:

xhr.open('POST', ...) – создаём POST-запрос.
xhr.send(formData)   – отсылаем форму серверу.

Методы объекта FormData

С помощью указанных ниже методов мы можем изменять поля в объекте FormData:

Технически форма может иметь много полей с одним и тем же именем name, поэтому несколько вызовов append добавят несколько полей с одинаковыми именами.

Ещё существует метод set, его синтаксис такой же, как у append. Разница в том, что .set удаляет все уже имеющиеся поля с именем name и только затем добавляет новое. То есть этот метод гарантирует, что будет существовать только одно поле с именем name, в остальном он аналогичен .append:

Поля объекта formData можно перебирать, используя цикл for..of:

let formData = new FormData();
formData.append('key1', 'value1');
formData.append('key2', 'value2');
formData.append('key3', 'value3');
var s='';
// Список пар ключ/значение
for(let [name, value] of formData) {s += `${name} = ${value}` + '\n'; 
}
alert(s);

Совместимость с браузерами

Особенность
Basic support1,07,01,01,2Да
send(ArrayBuffer)9,010,09,0?11,60
send(Blob)7,010,03,6?12,0
send(FormData)6,010,04,0?12,0
response10,010,06,0Да11,60
responseType = 'arraybuffer'10,010,06,0Да11,60
responseType = 'blob'19,010,06,0Да12,0
responseType = 'document'18,010,011,06,1Нет
responseType = 'json'31,0Нет10,0Да12,0 (17,0)
Progress Events7,010,03,5Да12,0
withCredentials3,010,03,54,012,0
timeout29,08,012,0Да12,0 (16,0)

Примеры

Пример 1

<script>
function loadXMLDoc(but)
  { but.disabled = true;
    var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest;
    var xhr = new XHR();
    xhr.onreadystatechange = function()
      { if (xhr.readyState == 4 && xhr.status == 200)
          {  document.getElementById("myDiv").innerHTML=xhr.responseText; }
      }
    xhr.open("GET","xhr/xmlhttp_info.txt",true);
    xhr.send();
  }
</script>

<h2>Использование объекта XMLHttpRequest</h2>
<div id="myDiv"></div>
<button type="button" onclick="loadXMLDoc(this)"> Попробуйте </button>



Пример 2

<script>
function loadXMLDoc(but,url)
  { but.disabled = true;
    var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest;
    var xhr = new XHR();
    xhr.onreadystatechange = function()
      { if (xhr.readyState == 4 && xhr.status == 200)
          {  document.getElementById("p1").innerHTML=xhr.getAllResponseHeaders(); }
      }
    xhr.open("GET",url,true);
    xhr.send();
  }
</script>

<p id="p1">Функция getAllResponseHeaders() возвращает заголовочную информацию ресурса, 
    наподобие длины, типа сервера, типа содержимого, последнего изменения и т.д.
</p>
<button type="button" onclick="loadXMLDoc(this,'xhr/xmlhttp_info.txt')"> 
  Получить заголовочную информацию 
</button>



Пример 3

<script>
function loadXMLDoc(but,url)
  { but.disabled = true;
    var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest;
    var xhr = new XHR();
    xhr.onreadystatechange = function()
      { if (xhr.readyState == 4 && xhr.status == 200)
         { document.getElementById("p1").innerHTML = 
           "Content-Type: " + xhr.getResponseHeader('Content-Type'); }
      }
    xhr.open("GET",url,true);
    xhr.send();
  }
</script>

<p id="p1">Функция getResponseHeader() используется, 
   чтобы получить информацию по конкретному заголовку для ресурса, 
   наподобие длины, типа сервера, типа содержимого, даты и времени последней модификации и т.д.
</p>
<button type="button" onclick="loadXMLDoc(this,'xhr/xmlhttp_info.txt')"> 
   Получить информацию по заголовку "Content-Type"
</button>



Пример 4

<script>
function showCustomer(str)
  { if (str=="") 
       { document.getElementById("d1").innerHTML="";  return; }  
    var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest;
    var xhr = new XHR();
    xhr.onreadystatechange = function()
      { if (xhr.readyState == 4 && xhr.status == 200)
         { document.getElementById("d1").innerHTML = xhr.responseText; }
      }
    xhr.open("GET","xhr/ex4.php?q="+str,true);
    xhr.send();
  }
</script>

<select onchange="showCustomer(this.value)">
<option value="">Изменить клиента:</option>
<option value="PETROVI">Иван Петров</option>
<option value="SIDOROVV">Василий Сидоров</option>
<option value="BYKOVA">Андрей Быков</option>
</select><br><br>

<div id="d1"> Информация о клиенте будет показана здесь...</div>



Пример 5

<script>
function loadXMLDoc(url)
  { var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest;
    var xhr = new XHR();
    xhr.onreadystatechange = function()
      { if (xhr.readyState == 4 && xhr.status == 200)
         { var txt="<table border='1'><tr>" + 
                   "<th>Заголовок</th>" +
                   "<th>Артист</th>"+
                   "</tr>";
           var x = xhr.responseXML.documentElement.getElementsByTagName("CD");
           for (i=0;i<x.length;i++)
             { txt=txt + "<tr>"; xx=x[i].getElementsByTagName("TITLE");
               try { txt=txt + "<td>" + xx[0].firstChild.nodeValue + "</td>"; }
               catch (er) { txt=txt + "<td> </td>"; }
               xx=x[i].getElementsByTagName("ARTIST");
               try { txt=txt + "<td>" + xx[0].firstChild.nodeValue + "</td>"; }
               catch (er) { txt=txt + "<td> </td>"; }
               txt=txt + "</tr>";
             }
          txt=txt + "</table>";
          document.getElementById('p1').innerHTML=txt;
        }
      }
    xhr.open("GET",url,true);
    xhr.send();
  }
</script>

<div id="p1"><h3> Получение содержимого XML файла </h3>
<button onclick="loadXMLDoc('xhr/cd_catalog.xml')"> 
  Получить информацию о CD дисках 
</button>
</div>



Пример 6

<script>
function showHint(str)
  { if (str.length==0) { document.getElementById("p1").innerHTML=""; return; }
    var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest;
    var xhr = new XHR();
    xhr.onreadystatechange = function()
      { if (xhr.readyState == 4 && xhr.status == 200)
         { document.getElementById("p1").innerHTML=xhr.responseText; }
      }
    xhr.open("GET","xhr/ex6.php?q="+encodeURIComponent(str),true);
    xhr.send();
  }
</script>

<h3>Начните печатать имя в поле ввода ниже:</h3>
Имя: <input type="text" id="txt1" onkeyup="showHint(this.value)" />
<p>Предложения: <span id="p1"></span></p> 



Пример 7

<style>
 li p {margin: 0 0 8px 40px;} 
</style>

<button onclick="loadPhones(this)"> Загрузить phones.json! </button>
<ul id="list"></ul>

<script>
function loadPhones(but) 
 { var xhr = new XMLHttpRequest();
   xhr.open('GET', 'xhr/phones.json', true);

   xhr.onreadystatechange = function() {
     if (xhr.readyState != 4) return;
     but.parentNode.removeChild(but);
     if (xhr.status != 200) { alert(xhr.status + ': ' + xhr.statusText); }
     else 
       { try { var phones = JSON.parse(xhr.responseText); } 
        catch (e) { alert("Некорректный ответ " + e.message); }
        showPhones(phones);
       }
     }
   xhr.send();
   but.innerHTML = 'Загружаю...';
   but.disabled = true;
 }
function showPhones(phones)
 { phones.forEach(function(phone) {
   var li = list.appendChild(document.createElement('li'));
   li.innerHTML = phone.name+'<p>'+phone.snippet+'</p>';  });
 }
</script>



Пример 8

<p>Современные браузеры, исключая IE9-, поддерживают встроенный объект FormData, 
 который кодирует формы для отправки на сервер.</p>
<form name="person">
<table>
 <tr><td style="text-align:right"> Имя: </td><td><input name="name" value="Никита"></td></tr>
 <tr><td style="text-align:right"> Фамилия: </td><td><input name="surname" value="Рябков"></td></tr>
</table>
</form>
<button onclick="sentForma(this)"> Отправить на сервер </button>
<div id="output"></div>
<script>
// создать объект для формы
var formData = new FormData(document.forms.person);

// добавить к пересылке ещё пару ключ - значение
formData.append("Age", "33");
formData.append("Profession", "Програмист");

function sentForma (but) 
  { var xhr = new XMLHttpRequest();
    xhr.onload  = function() {
      if (xhr.readyState == 4 && xhr.status == 200)
        { but.parentNode.removeChild(but);
          document.getElementById("output").innerHTML=xhr.responseText;
        }
     }
    xhr.open("POST", "xhr/ex8.php");
    xhr.send(formData); // отослать 
  }
</script>



Пример 9

// POST-запросы
var user = {
    name: "Tom",
    age: 23
};
 
var request = new XMLHttpRequest();
function reqReadyStateChange() {
    if (request.readyState == 4 && request.status == 200)
        document.getElementById("output").innerHTML= "На Ваш запрос отвечаю:<br>"+request.responseText;
}
var body = "name=" + user.name + "&age="+user.age;
request.open("POST", "xhr/postdata.php");
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
request.onreadystatechange = reqReadyStateChange;
request.send(body);
Предполагается, что данные отправляются скрипту на языке php postdata.php, который может иметь, например, следующее содержание:
<?php
$name = "Не известно";
$age = "Не известно";
if(isset($_POST['name'])) $name = $_POST['name'];
if (isset($_POST['age'])) $age = $_POST['age'];
echo "Ваше имя: $name  <br> Ваш возраст: $age";
?>

Пример 10. Определить форму в html и использовать ее для отправки

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
</head>
<body>
<div id="output"></div>
<form name="user" action="xhr/postdata.php">
<input type="text" name="name" placeholder="Введите имя" /><br/>
<input type="text" name="age" placeholder="Введите возраст" /><br/>
<input type="submit" name="submit" value="Отправить" />
</form>
<script>
// получаем объект формы
var form = document.forms.user;
// прикрепляем обработчик кнопки
form.submit.addEventListener("click", sendRequest);
 
// обработчик нажатия
function sendRequest(event){
     
    event.preventDefault();
    var formData = new FormData(form);
 
    var request = new XMLHttpRequest();
 
    request.open("POST", form.action);
     
    request.onreadystatechange = function () {
        if (request.readyState == 4 && request.status == 200)
            document.getElementById("output").innerHTML=request.responseText;
    }
    request.send(formData);
}
</script>
</body>
</html>