XMLHttpRequest
is a built-in browser object that allows to make HTTP requests in JavaScript.
Despite having the word “XML” in its name, it can operate on any data, not only in XML format. We can upload/download files, track progress and much more.
Right now, there’s another, more modern method fetch
, that somewhat deprecates XMLHttpRequest
.
In modern web-development XMLHttpRequest
is used for three reasons:
- Historical reasons: we need to support existing scripts with
XMLHttpRequest
. - We need to support old browsers, and don’t want polyfills (e.g. to keep scripts tiny).
- We need something that
fetch
can’t do yet, e.g. to track upload progress.
The basics
XMLHttpRequest has two modes of operation: synchronous and asynchronous.
Let’s see the asynchronous first, as it’s used in the majority of cases.
To do the request, we need 3 steps:
Create XMLHttpRequest
The constructor has no arguments.
Initialize it, usually right after new XMLHttpRequest
method
– HTTP-method. Usually"GET"
or"POST"
.URL
– the URL to request, a string, can be URL object.async
– if explicitly set tofalse
, then the request is synchronous, we’ll cover that a bit later.user
,password
– login and password for basic HTTP auth (if required).
Send it out
This method opens the connection and sends the request to server. The optional body
parameter contains the request body.
Some request methods like GET
do not have a body. And some of them like POST
use body
to send the data to the server. We’ll see examples of that later.
Listen to xhr
events for response
load
– when the request is complete (even if HTTP status is like 400 or 500), and the response is fully downloaded.error
– when the request couldn’t be made, e.g. network down or invalid URL.progress
– triggers periodically while the response is being downloaded, reports how much has been downloaded.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | xhr.onload = function() { alert(`Loaded: ${xhr.status} ${xhr.response}`); }; xhr.onerror = function() { // only triggers if the request couldn't be made at all alert(`Network Error`); }; xhr.onprogress = function(event) { // triggers periodically // event.loaded - how many bytes downloaded // event.lengthComputable = true if the server sent Content-Length header // event.total - total number of bytes (if lengthComputable) alert(`Received ${event.loaded} of ${event.total}`); }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | // 1. Create a new XMLHttpRequest object let xhr = new XMLHttpRequest(); // 2. Configure it: GET-request for the URL /article/.../load xhr.open('GET', '/article/xmlhttprequest/example/load'); // 3. Send the request over the network xhr.send(); // 4. This will be called after the response is received xhr.onload = function() { if (xhr.status != 200) { // analyze HTTP status of the response alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found } else { // show the result alert(`Done, got ${xhr.response.length} bytes`); // response is the server response } }; xhr.onprogress = function(event) { if (event.lengthComputable) { alert(`Received ${event.loaded} of ${event.total} bytes`); } else { alert(`Received ${event.loaded} bytes`); // no Content-Length } }; xhr.onerror = function() { alert("Request failed"); }; |
Once the server has responded, we can receive the result in the following xhr
properties:
status
- HTTP status code (a number):
200
,404
,403
and so on, can be0
in case of a non-HTTP failure. statusText
- HTTP status message (a string): usually
OK
for200
,Not Found
for404
,Forbidden
for403
and so on. response
(old scripts may useresponseText
)- The server response body.
We can also specify a timeout using the corresponding property
1 | xhr.timeout = 10000; // timeout in ms, 10 seconds |
Response Type
We can use xhr.responseType
property to set the response format:
""
(default) – get as string,"text"
– get as string,"arraybuffer"
– get asArrayBuffer
(for binary data, see chapter ArrayBuffer, binary arrays),"blob"
– get asBlob
(for binary data, see chapter Blob),"document"
– get as XML document (can use XPath and other XML methods) or HTML document (based on the MIME type of the received data),"json"
– get as JSON (parsed automatically).
Ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 | let xhr = new XMLHttpRequest(); xhr.open('GET', '/article/xmlhttprequest/example/json'); xhr.responseType = 'json'; xhr.send(); // the response is {"message": "Hello, world!"} xhr.onload = function() { let responseObj = xhr.response; alert(responseObj.message); // Hello, world! }; |
Ready states
XMLHttpRequest
changes between states as it progresses. The current state is accessible as xhr.readyState
.
All states, as in the specification:
1 2 3 4 5 | UNSENT = 0; // initial state OPENED = 1; // open called HEADERS_RECEIVED = 2; // response headers received LOADING = 3; // response is loading (a data packet 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:
1 2 3 4 5 6 7 8 | xhr.onreadystatechange = function() { if (xhr.readyState == 3) { // loading } if (xhr.readyState == 4) { // request finished } }; |
Aborting request
We can terminate the request at any time. The call to xhr.abort()
does that:
1 | xhr.abort(); // terminate the request |
That triggers abort
event, and xhr.status
becomes 0
.
Synchronous requests
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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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"); } |
HTTP-headers
XMLHttpRequest
allows both to send custom headers and read headers from the response.
setRequestHeader(name, value)
- Sets the request header with the given
name
andvalue
.1xhr.setRequestHeader('Content-Type', 'application/json');
getResponseHeader(name)
Gets the response header with the given
name
(exceptSet-Cookie
andSet-Cookie2
).1xhr.getResponseHeader('Content-Type')getAllResponseHeaders()
Returns all response headers, except
Set-Cookie
andSet-Cookie2
.1234Cache-Control: max-age=31536000Content-Length: 4260Content-Type: image/pngDate: Sat, 08 Sep 2012 16:53:16 GMTPOST, FormData
Syntax:
let formData = new FormData([form]); // creates an object, optionally fill fromformData.append(name, value); // appends a field
We create it, optionally fill from a form, append
more fields if needed, and then:
xhr.open('POST', ...)
– usePOST
method.xhr.send(formData)
to submit the form to the server.
Ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <form name="person"> <input name="name" value="John"> <input name="surname" value="Smith"> </form> <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); xhr.onload = () => alert(xhr.response); </script> |
The form is sent with multipart/form-data
encoding.
Upload progress
The progress
event triggers only on the downloading stage.
That is: if we POST
something, XMLHttpRequest
first uploads our data (the request body), then downloads the response.
If we’re uploading something big, then we’re surely more interested in tracking the upload progress. But xhr.onprogress
doesn’t help here.
There’s another object, without methods, exclusively to track upload events: xhr.upload
.
It generates events, similar to xhr
, but xhr.upload
triggers them solely on uploading:
loadstart
– upload started.progress
– triggers periodically during the upload.abort
– upload aborted.error
– non-HTTP error.load
– upload finished successfully.timeout
– upload timed out (iftimeout
property is set).loadend
– upload finished with either success or error.
Ex:
1 2 3 4 5 6 7 8 9 10 11 | 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}`); }; |
Cross-origin requests
XMLHttpRequest
can make cross-origin 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
:
1 2 3 4 5 | let xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.open('POST', 'http://anywhere.com/request'); ... |