Introduction to XMLHttpRequest (XHR)
Tags: webdev, javascript, codenewbie, beginners
Published: 2019-08-16 11:56:59


*This post was originally published on [attacomsian.com/blog](https://attacomsian.com/blog/http-requests-xhr).*

---

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](https://jsonplaceholder.typicode.com/) test REST API to send a GET request using XHR:

```javascript
// 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, use `xhr.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:

```javascript
xhr.open(method, URL, [async, user, password])
```

* `method` &mdash; HTTP request method. It can be `GET`, `POST`, `DELETE`, `PUT`, etc.
* `URL` &mdash; The URL to request, a string or a [URL object](https://attacomsian.com/blog/javascript-url-object)
* `asnyc` &mdash; Specify whether the request should be made asynchronously or not. Default value is `true`
* `username` & `password` &mdash; Credentials for basic HTTP authentication

The `open()` method does not open the connection to URL. It only configures the HTTP request. 

## `xhr.send()` Method

```javascript
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` &mdash; This event is called when the result is ready. It is equivalent to `xhr.onreadystatechange` event with `xhr.readyState == 4`.
* `error` &mdash; This event is fired when the request is failed due to network down or invalid URL.
* `progress` &mdash; This event is triggered periodically during the response download. It can be used to report progress for large network requests. 

```javascript
// 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:

```javascript
// 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 a `XMLHttpRequest` instance after following all redirects. This is the only way to retrieve `Location` header.

## Response Type

We can use `xhr.responseType` property to set the expected response format:

* Empty (default) or `text` &mdash; plain text
* `json` &mdash; parsed JSON
* `blob` &mdash; binary data Blob
* `document` &mdash; XML document
* `arraybuffer` &mdash; `ArrayBuffer` for binary data

Let's call a RESTful API to get the response as JSON:

```javascript
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) &mdash; The initial state
* `OPENED` (1) &mdash; The request begins
* `HEADERS_RECEIVED` (2) &mdash; The HTTP headers received
*  `LOADING` (3) &mdash; Response is loading
* ` DONE` (4) &mdash; The request is completed 

We can track the request state by using `onreadystatechange` event:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
// 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:

```javascript
// read response headers
xhr.getResponseHeader('Content-Type');
xhr.getResponseHeader('Cache-Control');
```

Want to get response headers at once? Use `getAllResponseHeaders()` instead:

```javascript
xhr.getAllResponseHeaders();
```

## XHR POST Request

The `XMLHttpRequest` POST request to submit a form data can be sent in two ways:

1. Using only Ajax
2. 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:

```javascript
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()](https://attacomsian.com/blog/json-parse-stringify#jsonstringify) and set the `Content-Type` header to `application/json`:

```javascript
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](https://attacomsian.com/blog/javascript-fetch-api#fetch-and-cookies), XHR does not send cookies and HTTP-authorization to another origin. To send cookies, you can use `withCredentials` property of the `xhr` object:

```javascript
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:

```javascript
$.ajax('https://jsonplaceholder.typicode.com/users')
    .done(data => {
        console.log(data);
    }).fail(err => {
        console.error('Error:', err);
    });
```

## XHR vs Fetch API

The [Fetch API](https://attacomsian.com/blog/javascript-fetch-api) is a [promise-based](https://attacomsian.com/blog/promises-javascript) modern alternative to XHR. It is clean, easier to understand, and massively used in [PWA Service Workers](https://attacomsian.com/blog/service-workers-javascript).

The XHR example above can be converted to a much simpler `fetch()`-based code that even automatically parses the returned JSON:

```javascript
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**](https://attacomsian.com/newsletter) to get web development tutorials & protips every week.

---

**Like this article?** [Follow @attacomsian on Twitter](https://twitter.com/attacomsian). You can also follow me on [LinkedIn](https://linkedin.com/in/attacomsian) and [DEV](https://dev.to/attacomsian).