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:
- Create a request object with
new XMLHttpRequest(). - Call
open(method, url)to say what you want. - Add an
onloadfunction that runs when the answer arrives. - 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.statusis the HTTP status code, just likeresponse.statuswith fetch.200means OK and404means not found.xhr.responseTextis the body as plain text.onerrorruns only when the request could not be sent at all, for example when you are offline. Like fetch, a404still runsonload, 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()beforesend(), and you should attachonloadbeforesend()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:
| Feature | XMLHttpRequest | fetch |
|---|---|---|
| Style | Events and callbacks (onload, onerror) | Promises, works with async/await |
| Setup | Many steps: create, open, set handlers, send | One function call |
| JSON | JSON.parse or responseType = "json" | await response.json() |
| Errors on 404/500 | Not an error, check status | Not an error, check response.ok |
| Cancel a request | xhr.abort() | AbortController |
| Upload progress | Yes, xhr.upload.onprogress | Not built in for uploads |
| Used in new code | Rarely | Yes, 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.onprogressreports 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.
XMLHttpRequestworks withopen(),send(),onloadandstatus; setresponseType = "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/awaitare shorter and easier to read. - Learn to recognise
XMLHttpRequestand jQuery$.ajaxso you can convert old code to fetch.