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 the URL, and send the request. Once the request completes, the object will contain information such as the response body and the HTTP status code.

Let's use JSONPlaceholder to 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()

The xhr.onload event only works in modern browsers (IE10+, Firefox, Chrome, Safari). If you want to support old browsers, use the xhr.onreadystatechange event instead.

xhr.open() Method

In the example above, we passed the HTTP method and a URL to the request to the 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 be GET, 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. The default value is true
  • username & password — Credentials for basic HTTP authentication

The open() method does not open the connection to the 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 invoked when the result is ready. It is equivalent to the xhr.onreadystatechange event with xhr.readyState == 4.
  • error — This event is fired when the request is failed due to a 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 specifying 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 an XMLHttpRequest instance after following all redirects. This is the only way to retrieve the Location header.

Response Type

We can use the 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
  • arraybufferArrayBuffer 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 the 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 the 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 an asynchronous request which is good for performance. But if you want to make an explicit synchronous request, just pass false as 3rd argument to the 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 on the end user's experience.

HTTP Headers

XMLHttpRequest allows us to set request headers and 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 get response header() 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

There are two ways to make a POST HTTP request using XMLHttpRequest: URL encoded form-data and FormData API.

XHR POST Request with with application/x-www-form-urlencoded

The following example demonstrates how you 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)
}

XHR POST Request with JSON Data

To make an XHR POST request with JSON data, you must 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 params = {
  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 the withCredentials property of the xhr object:

xhr.withCredentials = true

XHR vs. jQuery

jQuery wrapper methods like $.ajax() use XHR under the hood to provide a higher level of abstraction. 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))

Read JavaScript Fetch API guide to understand how you can use Fetch API to request network resources with just a few lines of code.

✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.