Complete JavaScript Course Module 12: Advanced: Asynchronous JavaScript

AJAX and XMLHttpRequest: The Older Way to Load Data

  • Beginner
  • Lesson 12.5
  • 6 min read
  • 56 of 76 in the course

In short

AJAX means loading data in the background without reloading the page. Learn how XMLHttpRequest works, how to read JSON with it, and how to turn old XHR code into fetch. What does AJAX mean? AJAX stands for Asynchronous JavaScript and XML. It is not one function or library.

AJAX means loading data in the background without reloading the page. Learn how XMLHttpRequest works, how to read JSON with it, and how to turn old XHR code into fetch.

What does AJAX mean?

AJAX stands for Asynchronous JavaScript and XML. It is not one function or library. It is the idea of asking a server for data in the background while the user keeps using the page, and then updating only part of the page with the answer. Before AJAX became popular in the mid-2000s, almost every click loaded a whole new page.

Think of a restaurant. The old way was like leaving the restaurant and coming back in every time you want more water. AJAX is like asking the waiter to bring water to your table while you keep eating.

The "XML" part is old history. At first, servers often sent data as XML. Today almost every API sends JSON, but the name AJAX stayed. People still say "make an AJAX call" when they mean any background request, even one made with fetch.

In the Fetch API lesson you learned the modern way. In this lesson you will learn the older tool, XMLHttpRequest (often shortened to XHR), so you can read and fix older code that you will meet in real projects.

How XMLHttpRequest works: open, send, onload and status

Using XHR always follows the same four steps:

  1. Create a request object with new XMLHttpRequest().
  2. Call open(method, url) to say what you want.
  3. Add an onload function that runs when the answer arrives.
  4. Call send() to actually send the request.
const xhr = new XMLHttpRequest();             // 1. create the request object
xhr.open("GET", "https://jsonplaceholder.typicode.com/users/1"); // 2. set method and URL

xhr.onload = function () {                     // 3. runs when the response arrives
  console.log(xhr.status);                     // Output: 200
  console.log(typeof xhr.responseText);        // Output: string (the raw JSON text)
};

xhr.onerror = function () {                    // runs only on a network failure
  console.log("Network error");
};

xhr.send();                                    // 4. send it
  • xhr.status is the HTTP status code, just like response.status with fetch. 200 means OK and 404 means not found.
  • xhr.responseText is the body as plain text.
  • onerror runs only when the request could not be sent at all, for example when you are offline. Like fetch, a 404 still runs onload, so you must check the status yourself.

You may also see onreadystatechange and readyState in very old code. readyState goes from 0 to 4, and 4 means "done". onload is the simpler, newer event that fires only when the request is done, so prefer it if you must write XHR.

Note: The order matters. You must call open() before send(), and you should attach onload before send() so you never miss the response.

How to read JSON with XMLHttpRequest

There are two ways to get a real JavaScript object from an XHR response. You can parse the text yourself with JSON.parse, or you can set responseType to "json" so the browser parses it for you.

function loadPost(id) {
  const xhr = new XMLHttpRequest();
  xhr.open("GET", `https://jsonplaceholder.typicode.com/posts/${id}`);
  xhr.responseType = "json"; // the browser parses the JSON for us

  xhr.onload = () => {
    if (xhr.status >= 200 && xhr.status < 300) {
      const post = xhr.response;       // already an object
      console.log("Title:", post.title);
    } else {
      console.log("Error: status", xhr.status);
    }
  };

  xhr.onerror = () => console.log("Network error");
  xhr.send();
}

loadPost(1);    // Output: Title: sunt aut facere repellat provident ...
loadPost(9999); // Output: Error: status 404

With responseType = "json", you read xhr.response (not responseText). The if check does the same job as response.ok in fetch.

Sending data with POST looks like this. You must set the Content-Type header yourself and turn the object into a string:

const xhr = new XMLHttpRequest();
xhr.open("POST", "https://jsonplaceholder.typicode.com/posts");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.responseType = "json";
xhr.onload = () => console.log(xhr.status, xhr.response.id);
// Output: 201 101
xhr.send(JSON.stringify({ title: "Hello", body: "My first post", userId: 1 }));

Why fetch replaced XMLHttpRequest

XHR still works in every browser and is not going away. But fetch is nicer to use for almost everything:

FeatureXMLHttpRequestfetch
StyleEvents and callbacks (onload, onerror)Promises, works with async/await
SetupMany steps: create, open, set handlers, sendOne function call
JSONJSON.parse or responseType = "json"await response.json()
Errors on 404/500Not an error, check statusNot an error, check response.ok
Cancel a requestxhr.abort()AbortController
Upload progressYes, xhr.upload.onprogressNot built in for uploads
Used in new codeRarelyYes, the standard choice

The biggest problem with XHR is callback hell. When one request depends on another, you put callbacks inside callbacks, and the code moves further and further to the right. With fetch and await, the same steps read from top to bottom like a recipe.

Tip: The one place XHR is still useful is showing a progress bar while uploading a big file, because xhr.upload.onprogress reports how many bytes were sent.

Recognising XHR and jQuery $.ajax in old code

Older websites often use jQuery, a popular library from before modern JavaScript. Its $.ajax function uses XHR inside. If you see code like this, it is an AJAX call:

// Old jQuery code (needs the jQuery library on the page)
$.ajax({
  url: "https://jsonplaceholder.typicode.com/users",
  method: "GET",
  dataType: "json",
  success: function (users) {
    console.log(users.length); // Output: 10
  },
  error: function () {
    console.log("Something went wrong");
  }
});

// Short versions you may also see:
// $.get(url, callback)   $.getJSON(url, callback)   $.post(url, data, callback)

Signs that you are looking at old AJAX code: new XMLHttpRequest(), onreadystatechange, readyState === 4, $.ajax, $.get, $.getJSON, and success: / error: options.

How to convert an XHR snippet to fetch

Here is a typical old snippet that shows user names in a list:

// BEFORE: XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://jsonplaceholder.typicode.com/users");
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4 && xhr.status === 200) {
    const users = JSON.parse(xhr.responseText);
    users.forEach(function (user) {
      console.log(user.name);
    });
  }
};
xhr.send();

And here is the same code with fetch, plus proper error handling that the old version did not have:

// AFTER: fetch with async/await
async function showUsers() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/users");
    if (!response.ok) throw new Error(`Status ${response.status}`);
    const users = await response.json();
    users.forEach((user) => console.log(user.name));
  } catch (error) {
    console.log("Could not load users:", error.message);
  }
}

showUsers();
// Output: Leanne Graham
// Output: Ervin Howell
// ... (10 names in total)

The steps to convert are always the same: replace open + send with fetch(url, options), replace JSON.parse(xhr.responseText) with await response.json(), replace the status check with response.ok, and replace onerror with try...catch.

Practice exercise

Write an XHR request that loads https://jsonplaceholder.typicode.com/todos/1 and logs the todo's title and completed values. Then rewrite the same thing as an async function using fetch. Both versions should log an error message if the status is not OK.

Hint: in the XHR version, set xhr.responseType = "json" and check xhr.status >= 200 && xhr.status < 300 inside onload.

Key takeaways

  • AJAX is the idea of loading data in the background without reloading the page, no matter which tool you use.
  • XMLHttpRequest works with open(), send(), onload and status; set responseType = "json" to get an object.
  • Like fetch, XHR does not treat 404 or 500 as an error, so always check the status.
  • Fetch replaced XHR because promises and async/await are shorter and easier to read.
  • Learn to recognise XMLHttpRequest and jQuery $.ajax so you can convert old code to fetch.