If the request does not succeed within the given time, it gets canceled and
timeout
event triggers.
We can use
xhr.responseType
property to set the response format:
""
(default) – get as string,
"text"
– get as string,
"arraybuffer"
– get as
ArrayBuffer
(for binary data, see chapter
ArrayBuffer, binary arrays
),
"blob"
– get as
Blob
(for binary data, see chapter
Blob
),
"document"
– get as XML document (can use XPath and other XML methods),
"json"
– get as JSON (parsed automatically).
For example, let’s get the response as JSON:
// the response is {"message": "Hello, world!"}
xhr.onload = function() {
let responseObj = xhr.response;
alert(responseObj.message); // Hello, world!
In the old scripts you may also find
xhr.responseText
and even
xhr.responseXML
properties.
They exist for historical reasons, to get either a string or XML document. Nowadays, we should set the format in
xhr.responseType
and get
xhr.response
as demonstrated above.
XMLHttpRequest
changes between states as it progresses. The current state is accessible as
xhr.readyState
.
All states, as in
the specification
:
UNSENT = 0; // initial state
OPENED = 1; // open called
HEADERS_RECEIVED = 2; // response headers received
LOADING = 3; // response is loading (a data packed is received)
DONE = 4; // request complete
An
XMLHttpRequest
object travels them in the order
0
→
1
→
2
→
3
→ … →
3
→
4
. State
3
repeats every time a data packet is received over the network.
We can track them using
readystatechange
event:
You can find
readystatechange
listeners in really old code, for historical reasons.
Nowadays,
load/error/progress
handlers deprecate it.
If in the
open
method the third parameter
async
is set to
false
, the request is made synchronously.
In other words, JavaScript execution pauses at
send()
and resumes when the response is received. Somewhat like
alert
or
prompt
commands.
Here’s the rewritten example, the 3rd parameter of
open
is
false
:
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/hello.txt', false);
try {
xhr.send();
if (xhr.status != 200) {
alert(`Error ${xhr.status}: ${xhr.statusText}`);
} else {
alert(xhr.response);
} catch(err) { // instead of onerror
alert("Request failed");
It might look good, but synchronous calls are used rarely, because they block in-page JavaScript till the loading is complete. In some browsers it becomes impossible to scroll. If a synchronous call takes too much time, the browser may suggest to close the “hanging” webpage.
Many advanced capabilities of XMLHttpRequest
, like requesting from another domain or specifying a timeout, are unavailable for synchronous requests. Also, as you can see, no progress indication.
Because of all that, synchronous requests are used very sparingly, almost never. We won’t talk about them any more.
XMLHttpRequest
allows both to send custom headers and read headers from the response.
There are 3 methods for HTTP-headers:
setRequestHeader(name, value)
Sets the request header with the given name
and value
.
For instance:
Several headers are managed exclusively by the browser, e.g. Referer
and Host
.
The full list is in the specification.
XMLHttpRequest is not allowed to change them, for the sake of user safety and correctness of the request.
Another peculiarity of XMLHttpRequest
is that one can’t undo setRequestHeader
.
Once the header is set, it’s set. Additional calls add information to the header, don’t overwrite it.
For instance:
getResponseHeader(name)
Gets the response header with the given name
(except Set-Cookie
and Set-Cookie2
).
For instance:
The line break between headers is always "\r\n"
(doesn’t depend on OS), so we can easily split it into individual headers. The separator between the name and the value is always a colon followed by a space ": "
. That’s fixed in the specification.
So, if we want to get an object with name/value pairs, we need to throw in a bit JS.
Like this (assuming that if two headers have the same name, then the latter one overwrites the former one):
.reduce((result, current) => {
let [name, value] = current.split(': ');
result[name] = value;
return result;
}, {});
Create it, optionally from a form,
append
more fields if needed, and then:
xhr.open('POST', ...)
– use
POST
method.
xhr.send(formData)
to submit the form to the server.
For instance:
<script>
// pre-fill FormData from the form
let formData = new FormData(document.forms.person);
// add one more field
formData.append("middle", "Lee");
// send it out
let xhr = new XMLHttpRequest();
xhr.open("POST", "/article/xmlhttprequest/post/user");
xhr.send(formData);
</script>
The form is sent with
multipart/form-data
encoding.
Or, if we like JSON more, then
JSON.stringify
and send as a string.
Just don’t forget to set the header
Content-Type: application/json
, many server-side frameworks automatically decode JSON with it:
xhr.open("POST", '/submit')
xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
xhr.send(json);
The
.send(body)
method is pretty omnivore. It can send almost everything, including Blob and BufferSource objects.
The
progress
event only works on the downloading stage.
That is: if we
POST
something,
XMLHttpRequest
first uploads our data, then downloads the response.
If we’re uploading something big, then we’re surely more interested in tracking the upload progress. But
progress
event doesn’t help here.
There’s another object
xhr.upload
, without methods, exclusively for upload events.
Here’s the list:
loadstart
– upload started.
progress
– triggers periodically during the upload.
abort
– upload aborted.
error
– non-HTTP error.
load
– upload finished successfully.
timeout
– upload timed out (if
timeout
property is set).
loadend
– upload finished with either success or error.
Example of handlers:
xhr.upload.onprogress = function(event) {
alert(`Uploaded ${event.loaded} of ${event.total} bytes`);
xhr.upload.onload = function() {
alert(`Upload finished successfully.`);
xhr.upload.onerror = function() {
alert(`Error during the upload: ${xhr.status}`);
Here’s a real-life example: file upload with progress indication:
// track upload progress
xhr.upload.onprogress = function(event) {
console.log(`Uploaded ${event.loaded} of ${event.total}`);
// track completion: both successful or not
xhr.onloadend = function() {
if (xhr.status == 200) {
console.log("success");
} else {
console.log("error " + this.status);
xhr.open("POST", "/article/xmlhttprequest/post/upload");
xhr.send(file);
</script>
XMLHttpRequest
can make cross-domain requests, using the same CORS policy as
fetch
.
Just like
fetch
, it doesn’t send cookies and HTTP-authorization to another origin by default. To enable them, set
xhr.withCredentials
to
true
:
xhr.onprogress = function(event) {
// report progress
alert(`Loaded ${event.loaded} of ${event.total}`);
xhr.onerror = function() {
// handle non-HTTP error (e.g. network down)
There are actually more events, the
modern specification
lists them (in the lifecycle order):
loadstart
– the request has started.
progress
– a data packet of the response has arrived, the whole response body at the moment is in
responseText
.
abort
– the request was canceled by the call
xhr.abort()
.
error
– connection error has occurred, e.g. wrong domain name. Doesn’t happen for HTTP-errors like 404.
load
– the request has finished successfully.
timeout
– the request was canceled due to timeout (only happens if it was set).