Episode 2 of 5

AJAX Requests

Learn how to make asynchronous HTTP requests using XMLHttpRequest (AJAX) — the original technique for fetching data from a server without reloading the page.

AJAX Requests

AJAX stands for Asynchronous JavaScript And XML. It is the technique of making HTTP requests from JavaScript without reloading the page. Despite the name, modern AJAX typically uses JSON instead of XML. In this episode you will learn how AJAX works using the XMLHttpRequest object.

What Is AJAX?

Before AJAX, getting new data from a server required a full page reload. AJAX changed everything — it lets you fetch data in the background and update parts of the page dynamically.

Without AJAXWith AJAX
Click a link → entire page reloadsClick a button → data loads in the background
Server sends a full HTML pageServer sends only the data (JSON/XML)
Screen flashes white during reloadPage updates smoothly, no flicker
Slow, bandwidth-heavyFast, lightweight

The XMLHttpRequest Object

XMLHttpRequest (XHR) is the original browser API for making AJAX requests. It has been available since the early 2000s and is supported in all browsers.

const xhr = new XMLHttpRequest();

xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');

xhr.onload = function() {
    if (xhr.status === 200) {
        const data = JSON.parse(xhr.responseText);
        console.log(data);
    }
};

xhr.send();

Breaking Down the Steps

StepCodeWhat It Does
1. Createnew XMLHttpRequest()Creates a new request object
2. Configurexhr.open('GET', url)Sets the HTTP method and URL (does not send yet)
3. Handle responsexhr.onload = function() {}Defines what happens when the response arrives
4. Sendxhr.send()Actually sends the request to the server

XHR Ready States

Before onload was widely supported, developers used onreadystatechange which fires at each stage of the request:

xhr.onreadystatechange = function() {
    console.log('Ready state:', xhr.readyState);
    if (xhr.readyState === 4 && xhr.status === 200) {
        console.log(JSON.parse(xhr.responseText));
    }
};
readyStateValueMeaning
UNSENT0Request created, open() not called yet
OPENED1open() has been called
HEADERS_RECEIVED2send() called, headers and status received
LOADING3Response body is being received
DONE4Request complete — response is fully received

The modern approach uses onload which fires only when the request is fully complete (readyState 4), making the code cleaner.

Practical Example: Fetching a List of Posts

const xhr = new XMLHttpRequest();

xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts');

xhr.onload = function() {
    if (xhr.status === 200) {
        const posts = JSON.parse(xhr.responseText);
        posts.forEach(post => {
            console.log(post.id + ': ' + post.title);
        });
    } else {
        console.log('Error: ' + xhr.status);
    }
};

xhr.onerror = function() {
    console.log('Network error occurred');
};

xhr.send();

Error Handling

EventWhen It Fires
onloadRequest completed (could be 200, 404, 500, etc.)
onerrorNetwork-level failure (DNS error, no internet, CORS block)
ontimeoutRequest took too long (if timeout is set)

Important: onload fires even for error HTTP status codes (404, 500). You must check xhr.status to know if the request was successful. onerror only fires for network-level failures where no HTTP response was received at all.

Sending POST Requests

const xhr = new XMLHttpRequest();

xhr.open('POST', 'https://jsonplaceholder.typicode.com/posts');

// Set the content type header for JSON data
xhr.setRequestHeader('Content-Type', 'application/json');

xhr.onload = function() {
    if (xhr.status === 201) {
        const newPost = JSON.parse(xhr.responseText);
        console.log('Created post:', newPost);
    }
};

// Send JSON data in the request body
xhr.send(JSON.stringify({
    title: 'My New Post',
    body: 'This is the post content.',
    userId: 1
}));

For POST requests, you pass the data as a string to xhr.send(). Use JSON.stringify() to convert a JavaScript object to a JSON string, and set the Content-Type header so the server knows what format to expect.

Creating a Reusable AJAX Function

function makeRequest(method, url, data, callback) {
    const xhr = new XMLHttpRequest();
    xhr.open(method, url);

    if (data) {
        xhr.setRequestHeader('Content-Type', 'application/json');
    }

    xhr.onload = function() {
        if (xhr.status >= 200 && xhr.status < 300) {
            callback(null, JSON.parse(xhr.responseText));
        } else {
            callback('Error: ' + xhr.status, null);
        }
    };

    xhr.onerror = function() {
        callback('Network error', null);
    };

    xhr.send(data ? JSON.stringify(data) : null);
}

// Usage:
makeRequest('GET', '/api/posts', null, function(err, data) {
    if (err) {
        console.log(err);
    } else {
        console.log(data);
    }
});

This wrapper function follows the Node.js error-first callback pattern — the first argument to the callback is an error (or null if successful), and the second is the data. This pattern will become important when we discuss callbacks in the next episode.

AJAX Is Asynchronous

console.log('Before request');

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
xhr.onload = function() {
    console.log('Response received');
};
xhr.send();

console.log('After request');

// Output:
// Before request
// After request
// Response received

Just like setTimeout, the request is sent and JavaScript immediately moves on. The onload callback runs later when the server responds. This is the fundamental async pattern that the rest of this series builds on.

XHR vs Modern Alternatives

FeatureXMLHttpRequestFetch API
SyntaxVerbose, event-basedClean, Promise-based
PromisesNo (callback-based)Yes (built-in)
StreamsNoYes
Cancel requestsxhr.abort()AbortController
Browser supportAll browsersModern browsers

fetch() is the modern replacement for XMLHttpRequest, but understanding XHR first gives you a solid foundation for how async HTTP requests work under the hood.

Key Takeaways

  • AJAX lets you fetch data from a server without reloading the page
  • XMLHttpRequest is created, configured with open(), given a callback with onload, and fired with send()
  • onload fires when the response is received — but you must check xhr.status for success
  • onerror fires only on network-level failures, not HTTP error codes
  • POST requests require setRequestHeader('Content-Type', 'application/json') and JSON.stringify()
  • AJAX requests are asynchronous — code after xhr.send() runs immediately, before the response arrives
  • The Fetch API is the modern replacement, but understanding XHR builds your async fundamentals