This post was originally published on attacomsian.com/blog.
XMLHttpRequest (XHR) was invented by Microsoft in the early '90s and became a go-to technology for asynchronous server interactions during the mid of the first decade of the 21st century.
Thanks to XHR, for the first time, it became possible to update parts of a web page without reloading the whole page.
XMLHttpRequest
is a built-in browser object in all modern browsers that can be used to make HTTP requests in JavaScript to exchange data between the web browser and the server.
Despite the word "XML" in its name, XMLHttpRequest
can be used to retrieve any kind of data, and not just XML. We can use it to upload/download files, submit form data, track progress and much more.
Basic XHR Request
To send an HTTP request using XHR, create an XMLHttpRequest
object, open a connection to URL, and send the request. Once the request completes, the object will contain useful information such as the response body and the HTTP status code.
Let's use JSONPlaceholder test REST API to send a GET request using XHR:
// create an XHR object
const xhr = new XMLHttpRequest();
// listen for `onload` event
xhr.onload = () => {
// process response
if (xhr.status == 200) {
// parse JSON data
console.log(JSON.parse(xhr.response));
} else {
console.error('Error!');
}
};
// create a `GET` request
xhr.open('GET', 'https://jsonplaceholder.typicode.com/users');
// send request
xhr.send();
xhr.onload
event only works in modern browsers (IE10+, FireFox, Chrome, Safari). If you want to support old browsers, usexhr.onreadystatechange
event instead.
xhr.open()
Method
In the example above, we passed the HTTP method and a URL to the request to open()
method. This method is normally called right after new XMLHttpRequest()
. We can use this method to specify the main parameters of the request:
Here is the syntax of this method:
xhr.open(method, URL, [async, user, password])
-
method
— HTTP request method. It can beGET
,POST
,DELETE
,PUT
, etc. -
URL
— The URL to request, a string or a URL object -
asnyc
— Specify whether the request should be made asynchronously or not. Default value istrue
-
username
&password
— Credentials for basic HTTP authentication
The open()
method does not open the connection to URL. It only configures the HTTP request.
xhr.send()
Method
xhr.send([body])
The send()
method opens the network connection and sends the request to the server. It takes an optional body
parameter that contains the request body. For request methods like GET
you do not need to pass the body parameter.
XHR Events
The three most widely used XHR events are the following:
-
load
— This event is called when the result is ready. It is equivalent toxhr.onreadystatechange
event withxhr.readyState == 4
. -
error
— This event is fired when the request is failed due to network down or invalid URL. -
progress
— This event is triggered periodically during the response download. It can be used to report progress for large network requests.
// listen for `load` event
xhr.onload = () => {
console.log(`Data Loaded: ${xhr.status} ${xhr.response}`);
};
// listen for `error` event
xhr.onerror = () => {
console.error('Request failed.');
}
// listen for `progress` event
xhr.onprogress = (event) => {
// event.loaded returns how many bytes are downloaded
// event.total returns the total number of bytes
// event.total is only available if server sends `Content-Length` header
console.log(`Downloaded ${event.loaded} of ${event.total}`);
}
Request Timeout
You can easily configure the request timeout by specify the time in milliseconds:
// set timeout
xhr.timeout = 5000; // 5 seconds
// listen for `timeout` event
xhr.ontimeout = () => console.log('Request timeout.', xhr.responseURL);
xhr.responseURL
property returns the final URL of aXMLHttpRequest
instance after following all redirects. This is the only way to retrieveLocation
header.
Response Type
We can use xhr.responseType
property to set the expected response format:
- Empty (default) or
text
— plain text -
json
— parsed JSON -
blob
— binary data Blob -
document
— XML document -
arraybuffer
—ArrayBuffer
for binary data
Let's call a RESTful API to get the response as JSON:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.jsonbin.io/b/5d5076e01ec3937ed4d05eab/1');
// set response format
xhr.responseType = 'json';
xhr.send();
xhr.onload = () => {
// get JSON response
const user = xhr.response;
// log details
console.log(user.name); // John Doe
console.log(user.email); // john.doe@example.com
console.log(user.website); // http://example.com
}
Request States (xhr.readyState
)
The XMLHttpRequest
object changes state as the request progresses. We can access the current state using xhr.readyState
property.
The states are:
-
UNSENT
(0) — The initial state -
OPENED
(1) — The request begins -
HEADERS_RECEIVED
(2) — The HTTP headers received -
LOADING
(3) — Response is loading -
DONE
(4) — The request is completed
We can track the request state by using onreadystatechange
event:
xhr.onreadystatechange = function () {
if(xhr.readyState == 1) {
console.log('Request started.');
}
if(xhr.readyState == 2) {
console.log('Headers received.');
}
if (xhr.readyState == 3) {
console.log('Data loading..!');
}
if (xhr.readyState == 4) {
console.log('Request ended.');
}
};
Aborting Request
We can easily abort an XHR request anytime by calling the abort()
method on the xhr
object:
xhr.abort(); // cancel request
Synchronous Requests
By default, XHR makes asynchronous requests which is good for performance. But if you want to make an explicit synchronous request, just pass false
as 3rd argument to open()
method. It will pause the JavaScript execution at send()
and resume when the response is available:
xhr.open('GET', 'https://api.jsonbin.io/b/5d5076e01ec3937ed4d05eab/1', false);
Be careful! Chrome display the following warning for synchronous XHR request: [Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience.
HTTP Headers
XMLHttpRequest
allows us to set request headers as well as read response headers. We can set the request Content-Type
& Accept
headers by calling setRequestHeader()
method on the xhr
object:
// set request headers
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Accept', '*/*'); // accept all
Similarly if you want to read the response headers (except Set-Cookie
), call getResponseHeader()
on the xhr
object:
// read response headers
xhr.getResponseHeader('Content-Type');
xhr.getResponseHeader('Cache-Control');
Want to get response headers at once? Use getAllResponseHeaders()
instead:
xhr.getAllResponseHeaders();
XHR POST Request
The XMLHttpRequest
POST request to submit a form data can be sent in two ways:
- Using only Ajax
- Using
FormData
API
The first approach is good enough unless you want to upload a file and need multipart/form-data
encoding. Here is how we can make a POST request with URL encoded form data:
const xhr = new XMLHttpRequest();
// configure a `POST` request
xhr.open('POST', '/login');
// prepare form data
let params = 'username=attacomsian&password=123456';
// set `Content-Type` header
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// pass `params` to `send()` method
xhr.send(params);
// listen for `load` event
xhr.onload = () => {
console.log(xhr.responseText);
}
Want to make a JSON POST request? Make sure you convert the JSON data into a string using JSON.stringify() and set the Content-Type
header to application/json
:
const xhr = new XMLHttpRequest();
// configure a `POST` request
xhr.open('POST', '/login');
// create a JSON object
const json = {
username: 'attacomsian',
password: '123456'
};
// set `Content-Type` header
xhr.setRequestHeader('Content-Type', 'application/json');
// pass `params` to `send()` method
xhr.send(JSON.stringify(params));
// listen for `load` event
xhr.onload = () => {
console.log(xhr.responseText);
}
Cross-Origin Requests & Cookies
XMLHttpRequest
can send cross-origin requests, but it is subjected to special security measures. To request a resource from a different server, the server must explicitly support this using CORS (Cross-Origin Resource Sharing).
Just like Fetch API, XHR does not send cookies and HTTP-authorization to another origin. To send cookies, you can use withCredentials
property of the xhr
object:
xhr.withCredentials = true;
XHR vs jQuery
jQuery wrapper methods like $.ajax()
uses XHR under the hood and provides a higher-level of abstraction to make developers life easy. Using jQuery, we can translate the above code into just a few lines:
$.ajax('https://jsonplaceholder.typicode.com/users')
.done(data => {
console.log(data);
}).fail(err => {
console.error('Error:', err);
});
XHR vs Fetch API
The Fetch API is a promise-based modern alternative to XHR. It is clean, easier to understand, and massively used in PWA Service Workers.
The XHR example above can be converted to a much simpler fetch()
-based code that even automatically parses the returned JSON:
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error('Error:', err));
✌️ I write about modern JavaScript, Node.js, Spring Boot, and all things web development. Subscribe to my newsletter to get web development tutorials & protips every week.
Like this article? Follow @attacomsian on Twitter. You can also follow me on LinkedIn and DEV.
Top comments (9)
Fetch <3
One correction or at least clarification to consider is that you can't download (in a common nomenclature sense) a file with XHR. That is to say you have no way of saving it locally like most users might assume when they think of downloading. You can retrieve a file's contents which is technically downloading, but you can only do things with it within your app. There's no way to offer the user a save dialog, nor can you save to a user's hard drive through JavaScript alone.
The other side-note is that your javascript syntax is not very backwards compatible. Your jQuery example solves that problem, but users on IE are going to have parse errors with your vanilla code that could stop all javascript on the page once that code is encountered.
So that’s not exactly true on the file downloading....you can still stream file contents back to the local machine, create an object url, set it as the href on a created a tag, append it to the body, fire the “click” method on the element, and then remove the element from the DOM. It will bring up the OS’s download dialog as if you hit a “download” button.
I suppose if you needed a temporary or auto-generated file to have immediate expiration on the server that would be a very creative way of getting around returning a download URL that I hadn't thought of.
That's fair. The only reason I responded quick is because on our platform that's how we make downloadable CSVs available - ie we take a JSON response from the server, parse it back into CSV and then do what I said above
Yeah, we do a lot of similar work, but we use either a download handler or a temporary file system with expiration policy to generate the download (csv/pdf/etc) server-side. We do have one app that that could benefit from your approach though where we parse a user-provided CSV in javascript, let them manipulate, but then we are pushing that data to the server for file generation to retrieve the modified version of their original CSV data. I'll be considering a rework on that since there's literally no reason for it to ever go to the server if we use your approach.
Thank you for the feedback.
Although XHR does not trigger file download, you can still use it to download files. Here is an example I found on the internet.
For backward compatibility, I believe, JavaScript is much more evolved in recent years. We should follow the best modern practices and learn to live without IE. There is a whole new universe beyond IE.
Thanks for the reminder of how painful XHRs are to use directly 😂 Fetch is so much nicer!
😂
Fetch is, no doubt, much cleaner and modern.