Browser APIs & Web APIs
XMLHttpRequest (Legacy)
Master legacy AJAX in JavaScript. Learn the XMLHttpRequest API structure, readyState lifecycles, processing events, and refactoring to Fetch API.
1. Introduction
Before the Fetch API was introduced, XMLHttpRequest (XHR) was the standard API for making asynchronous network requests (AJAX) in the browser. Unlike the Fetch API, which is promise-based, XHR is event-based and uses state tracking.
2. Why It Matters
While the Fetch API is preferred for modern projects, XHR is still used in older codebases and legacy libraries. Understanding how XHR handles state lifecycles and handles uploads is important for maintaining legacy applications and refactoring them to use the Fetch API.
3. Real-World Analogy
Think of a Pneumatic Tube Messaging System:
- Fetch API (Modern Courier): You hand a package to a courier. The courier returns a promise ticket. You wait until they return with the delivery (resolved) or report a failure (rejected).
- XMLHttpRequest (Legacy Tube): You insert your capsule into the tube. You must monitor a physical dial that moves as the capsule travels: "Sent... Reached Server... Processing... Finished." You register listeners to react when the dial reaches the "Finished" state.
4. The XHR State Lifecycle
An XHR request progresses through five states, tracked by the readyState property:
• 0 (UNSENT): Client has been created. open() has not been called yet.
• 1 (OPENED): open() has been called.
• 2 (HEADERS_RECEIVED): send() has been called, and response headers have been received.
• 3 (LOADING): Response body is downloading. responseText contains partial data.
• 4 (DONE): The operation is complete. All data has been downloaded.
5. XHR features not easily done in early Fetch
In early versions of the Fetch API, XHR was still preferred for two main features:
• Upload Progress Tracking: XHR exposes an xhr.upload object that fires progress events, which is useful for building file upload progress bars.
• Synchronous Requests: Setting the third argument of open() to false executes the request synchronously, blocking main thread execution. Note that this is deprecated in modern browsers.
6. Practical Example
This script demonstrates how to wrap a legacy XHR request inside a Promise, allowing you to use it with async/await:
7. Common Mistakes
- Accessing responseText before readyState is 4: Attempting to read
xhr.responseTextwhen readyState is 2 or 3 can return incomplete data or throw errors. Check that readyState is 4 before reading response data. - Forgetting to call send(): Setting up the connection using
open()but forgetting to trigger the request by callingsend().
8. Quick Quiz
Q1: Which readyState value represents that the connection is complete and all response data has been downloaded?
A) readyState === 3
B) readyState === 4
Answer: B — readyState 4 (DONE) indicates that the request has completed and the response is fully downloaded.
9. Scenario-Based Challenge
The Legacy File Uploader Refactor:
An older upload module uses an XHR listener: xhr.upload.onprogress. You are refactoring the application to use the Fetch API. Explain how to implement progress tracking in the Fetch API or if XHR should be kept for this specific use case.
10. Debugging Exercise
Explain why this event handler never triggers:
const xhr = new XMLHttpRequest(); xhr.open('GET', '/api/users'); xhr.send();
// Bug: handler is registered after sending the request! xhr.onload = function() { console.log(xhr.responseText); // sometimes logs nothing or doesn't fire! Why? };
View Solution
Diagnosis: The event listener is registered after calling send(). If the request completes quickly (for example, if the response is cached locally), the load event fires before the listener is registered, causing the callback to be missed.
Fix: Register all event handlers before calling send():
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users');
xhr.onload = function() {
console.log(xhr.responseText);
};
xhr.send(); // Send request after registering handlers
11. Interview Questions
🟢 Q1: Compare XMLHttpRequest and the Fetch API.
Answer:
• Syntax: XHR uses an event-based structure that requires checking readyState and status. The Fetch API is promise-based, which integrates with async/await to write cleaner code.
• Error Handling: XHR triggers the error callback on HTTP error status codes. Fetch resolves successfully on HTTP errors (like 404 or 500), only rejecting on network failures.
• Upload Progress: XHR supports native upload progress tracking via the xhr.upload.onprogress event. Fetch does not support upload progress tracking natively.
12. Production Considerations
- • Legacy Browser Support: In modern web development, default to the Fetch API. Use XHR only if you need to support old browsers (like IE11) without using fetch polyfills, or if you need to track file upload progress natively.