Legacy JS & AJAX
Understand the foundational asynchronous revolution: XMLHttpRequest (XHR) state machines, the 5 readyState codes, XML parsing via DOMParser, and bridging legacy legacy enterprise codebases to modern Fetch.
Classical Full Page Reload vs Asynchronous AJAX
Before AJAX, clicking a link or submitting a form required the web browser to destroy the current page and render a blank white screen while downloading the entire new HTML document from scratch.
1. The XMLHttpRequest State Machine (5 ReadyStates)
Unlike fetch() which relies on Promises, XMLHttpRequest is an event-driven state machine. As the request progresses from initial setup to response arrival, its readyState transitions through five numerical states:
| readyState | State Constant | Description & Available Properties |
|---|---|---|
| 0 | XMLHttpRequest.UNSENT |
Client object instantiated, but open() has not yet been called. |
| 1 | XMLHttpRequest.OPENED |
open(method, url) has been invoked. Request headers can now be set via setRequestHeader(). |
| 2 | XMLHttpRequest.HEADERS_RECEIVED |
send() has been called. Response headers and HTTP status codes are now accessible. |
| 3 | XMLHttpRequest.LOADING |
Response payload is actively streaming. responseText contains partial bytes. |
| 4 | XMLHttpRequest.DONE |
Data transfer complete. Full payload is ready for inspection. |
The Canonical Classic XHR Pattern
const xhr = new XMLHttpRequest();
// Configure HTTP method, target endpoint, and async boolean
xhr.open('GET', 'https://api.example.com/users', true);
// Set custom request headers (MUST be after open, before send)
xhr.setRequestHeader('Accept', 'application/json');
// Listen to state transitions
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log('Received data:', data);
} else {
console.error(`HTTP error: ${xhr.status}`);
}
}
};
// Initiate network socket dispatch
xhr.send();
Run in Playground
2. Upload Progress Tracking & XML Parsing
Even today in modern web development, XMLHttpRequest has one unique capability that modern fetch() cannot easily match without complex Streams: accurate upload progress monitoring.
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
progressBar.style.width = `${percent}%`;
}
};
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
const title = xmlDoc.getElementsByTagName('title')[0].textContent;
Interactive Lab: XHR ReadyState Simulator
State Machine
Step through an XMLHttpRequest lifecycle step-by-step. Witness how internal properties like readyState, status, and responseText materialize.
Promisify XMLHttpRequest into a Modern Async Utility
In legacy maintenance work, you will frequently need to modernize legacy callback-based XHR endpoints. Wrap XMLHttpRequest inside a modern ajax(url, options) function that returns a Promise resolving with the parsed JSON data or rejecting on error or non-2xx status codes.
function ajax(url, { method = 'GET', headers = {}, body = null } = {}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
// Set request headers
Object.entries(headers).forEach(([key, val]) => {
xhr.setRequestHeader(key, val);
});
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
try {
resolve(JSON.parse(xhr.responseText));
} catch {
resolve(xhr.responseText);
}
} else {
reject(new Error(`HTTP Error ${xhr.status}: ${xhr.statusText}`));
}
};
xhr.onerror = () => reject(new Error('Network error occurred'));
xhr.ontimeout = () => reject(new Error('Request timed out'));
xhr.send(body);
});
}
Chapter 36 Knowledge Check
Validate your understanding of legacy AJAX, readyStates, and network communication.
xhr.setRequestHeader() be called?