JavaScript XMLHttpRequest
Introduction
XMLHttpRequest (XHR) is one of the foundational technologies that enabled modern web applications to communicate with servers without requiring a full page refresh. Developed initially by Microsoft for Internet Explorer, it has become a standard feature in all major browsers and was the primary method for asynchronous communication before the introduction of the more modern Fetch API.
Despite its name, XMLHttpRequest isn't limited to handling only XML data—it can work with any text-based format, including JSON, HTML, and plain text. XHR is the technology behind AJAX (Asynchronous JavaScript And XML), which revolutionized web applications by making them more interactive and responsive.
Understanding XMLHttpRequest
XMLHttpRequest provides methods to send HTTP requests to a server and load the server response data directly back into the script. This enables web pages to update content without reloading the entire page, creating a smoother user experience.
Basic Syntax
// Create a new XMLHttpRequest object
const xhr = new XMLHttpRequest();
// Configure it: GET-request for the URL
xhr.open('GET', 'https://api.example.com/data', true);
// Send the request
xhr.send();
The open()
method takes at least three parameters:
- HTTP method (like GET, POST, PUT, DELETE)
- URL to send the request to
- A boolean indicating whether the request should be asynchronous (true) or synchronous (false)
Handling Responses
To process the server's response, we need to set up event handlers. The most commonly used event is onload
, which fires when the request completes successfully.
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
// Set up response handler
xhr.onload = function() {
if (xhr.status === 200) {
console.log('Response received:', xhr.responseText);
} else {
console.error('Request failed with status:', xhr.status);
}
};
// Handle network errors
xhr.onerror = function() {
console.error('Network error occurred');
};
xhr.send();
Properties of XMLHttpRequest
The XMLHttpRequest object provides several useful properties:
status
: HTTP status code (e.g., 200 for success, 404 for not found)statusText
: HTTP status messageresponseText
: The response as a stringresponseXML
: The response as an XML document (if applicable)readyState
: Current state of the request (0-4)
The readyState Values
The readyState
property can have the following values:
0
(UNSENT): The object has been created butopen()
hasn't been called1
(OPENED):open()
has been called2
(HEADERS_RECEIVED):send()
has been called and headers have been received3
(LOADING): Response body is being received4
(DONE): The operation is complete
You can track these state changes using the onreadystatechange
event:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log('Request completed successfully');
console.log(xhr.responseText);
} else {
console.error('Request failed');
}
}
};
xhr.send();
Sending Data with POST Requests
To send data to a server, you typically use a POST request:
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/submit', true);
// Set the content type header for form data
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// Set up response handler
xhr.onload = function() {
if (xhr.status === 200) {
console.log('Data submitted successfully');
}
};
// Prepare the data
const data = '[email protected]';
// Send the request with data
xhr.send(data);
Sending JSON Data
For modern APIs that accept JSON, you would set a different content type:
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/submit', true);
// Set the content type header for JSON
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
console.log('Server response:', response);
}
};
// Create and stringify the data object
const data = {
name: 'John',
email: '[email protected]'
};
// Send the JSON string
xhr.send(JSON.stringify(data));
Practical Example: Loading User Data
Let's build a simple example that loads user data from a public API and displays it on a webpage:
function fetchUserData(userId) {
const xhr = new XMLHttpRequest();
// Configure the request
xhr.open('GET', `https://jsonplaceholder.typicode.com/users/${userId}`, true);
// Set up handlers
xhr.onload = function() {
if (xhr.status === 200) {
const user = JSON.parse(xhr.responseText);
displayUserData(user);
} else {
displayError(`Failed to load user data. Status: ${xhr.status}`);
}
};
xhr.onerror = function() {
displayError('Network error occurred');
};
// Send the request
xhr.send();
}
function displayUserData(user) {
const userInfo = document.getElementById('user-info');
userInfo.innerHTML = `
<h2>${user.name}</h2>
<p><strong>Email:</strong> ${user.email}</p>
<p><strong>Phone:</strong> ${user.phone}</p>
<p><strong>Website:</strong> ${user.website}</p>
<p><strong>Company:</strong> ${user.company.name}</p>
`;
}
function displayError(message) {
const userInfo = document.getElementById('user-info');
userInfo.innerHTML = `<p class="error">${message}</p>`;
}
// Usage
document.getElementById('load-user').addEventListener('click', function() {
const userId = document.getElementById('user-id').value || 1;
fetchUserData(userId);
});
HTML to accompany this script:
<div class="user-container">
<div class="controls">
<input type="number" id="user-id" placeholder="Enter user ID" min="1" max="10">
<button id="load-user">Load User Data</button>
</div>
<div id="user-info">
<!-- User data will be displayed here -->
</div>
</div>
Error Handling and Timeouts
Setting a Request Timeout
You can set a timeout for the request using the timeout
property:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
// Set 5-second timeout
xhr.timeout = 5000;
xhr.ontimeout = function() {
console.error('Request timed out');
};
xhr.send();
Handling Different Types of Errors
A comprehensive error handling approach might look like this:
function makeRequest(url, method, data = null) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
if (method === 'POST' && data) {
xhr.setRequestHeader('Content-Type', 'application/json');
}
xhr.timeout = 10000; // 10 seconds
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const response = JSON.parse(xhr.responseText);
resolve(response);
} catch (e) {
// If response is not valid JSON
resolve(xhr.responseText);
}
} else {
reject({
status: xhr.status,
statusText: xhr.statusText,
message: `Request failed with status: ${xhr.status}`
});
}
};
xhr.onerror = function() {
reject({
status: 0,
statusText: 'Network Error',
message: 'A network error occurred'
});
};
xhr.ontimeout = function() {
reject({
status: 0,
statusText: 'Timeout',
message: 'The request timed out'
});
};
if (data) {
xhr.send(JSON.stringify(data));
} else {
xhr.send();
}
});
}
// Usage
makeRequest('https://jsonplaceholder.typicode.com/users/1', 'GET')
.then(data => console.log('User data:', data))
.catch(error => console.error('Error:', error.message));
Working with Progress Events
XMLHttpRequest provides events that let you track the progress of uploads and downloads, which is especially useful for file transfers:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com/large-file.zip');
// Track download progress
xhr.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log(`Download progress: ${percentComplete.toFixed(2)}%`);
// Update progress bar in UI
document.getElementById('progress-bar').value = percentComplete;
}
};
// Track upload progress (for POST requests with large data)
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log(`Upload progress: ${percentComplete.toFixed(2)}%`);
}
};
xhr.onload = function() {
console.log('Transfer complete');
};
xhr.send();
Summary
XMLHttpRequest is a fundamental tool for asynchronous communication in JavaScript. While it has been largely superseded by the more modern Fetch API, understanding XHR is still important for several reasons:
- It helps you understand the history and foundations of AJAX programming
- You may encounter it in legacy code that you need to maintain
- It offers certain features like progress events that were only recently added to Fetch
- It has broader browser support compared to newer APIs
Key points to remember about XMLHttpRequest:
- It allows sending HTTP requests asynchronously
- You can use it to make GET, POST, PUT, DELETE, and other HTTP requests
- It provides numerous events and properties to handle responses and errors
- It can work with any text-based data format, not just XML
- Understanding the different readyState values helps you track request progress
Additional Resources and Exercises
Resources
Exercises
-
Basic Data Fetcher: Create a simple web page that fetches and displays data from a public API of your choice using XMLHttpRequest.
-
User Search: Build a form that allows users to search for GitHub profiles using the GitHub API and XMLHttpRequest. Display the user's avatar, name, bio, and repository count.
-
Form Submitter: Create an HTML form and use XMLHttpRequest to submit it without refreshing the page. Display a success message when the submission is complete.
-
File Uploader: Build a file upload interface that shows progress using XMLHttpRequest's progress events.
-
Error Handler Challenge: Implement comprehensive error handling for an XMLHttpRequest that makes requests to unreliable endpoints (sometimes returns errors, sometimes times out). Display appropriate user-friendly messages for each type of error.
By mastering XMLHttpRequest, you'll have a solid foundation for understanding more modern APIs like Fetch, and you'll be better equipped to work with any JavaScript codebase, whether new or legacy.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)