XMLHttpRequest and AJAX in JavaScript
Introduction
XMLHttpRequest (XHR) is the foundation of AJAX (Asynchronous JavaScript and XML), which enables web pages to send and receive data asynchronously without reloading. While modern applications often use the Fetch API, understanding XMLHttpRequest is important for legacy code and understanding how AJAX works. In this article, you’ll learn how to use XMLHttpRequest, handle responses, and implement practical AJAX patterns.
Understanding XMLHttpRequest
Creating an XMLHttpRequest
// Create a new XMLHttpRequest object
const xhr = new XMLHttpRequest();
// Check browser support
if (window.XMLHttpRequest) {
console.log('XMLHttpRequest is supported');
} else {
console.log('XMLHttpRequest is not supported');
}
Basic GET Request
// Simple GET request
const xhr = new XMLHttpRequest();
// Configure the request
xhr.open('GET', '/api/users', true);
// Set up event handlers
xhr.onload = function() {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log('Data received:', data);
} else {
console.error('Error:', xhr.status);
}
};
xhr.onerror = function() {
console.error('Request failed');
};
// Send the request
xhr.send();
XMLHttpRequest Properties
// Important XMLHttpRequest properties
const xhr = new XMLHttpRequest();
// readyState values:
// 0 - UNSENT: request not initialized
// 1 - OPENED: open() called
// 2 - HEADERS_RECEIVED: headers received
// 3 - LOADING: response being downloaded
// 4 - DONE: operation complete
xhr.onreadystatechange = function() {
console.log('Ready state:', xhr.readyState);
if (xhr.readyState === 4) {
console.log('Status:', xhr.status);
console.log('Response:', xhr.responseText);
console.log('Response headers:', xhr.getAllResponseHeaders());
}
};
xhr.open('GET', '/api/data', true);
xhr.send();
HTTP Methods with XMLHttpRequest
GET Request
// GET request
function getRequest(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function() {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
};
xhr.onerror = () => reject(new Error('Network error'));
xhr.send();
});
}
// Usage
getRequest('/api/users')
.then(data => console.log(data))
.catch(error => console.error(error));
POST Request
// POST request with data
function postRequest(url, data) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status === 201 || xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
};
xhr.onerror = () => reject(new Error('Network error'));
xhr.send(JSON.stringify(data));
});
}
// Usage
postRequest('/api/users', {
name: 'John',
email: '[email protected]'
})
.then(response => console.log('User created:', response))
.catch(error => console.error(error));
PUT Request
// PUT request to update resource
function putRequest(url, data) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
};
xhr.onerror = () => reject(new Error('Network error'));
xhr.send(JSON.stringify(data));
});
}
// Usage
putRequest('/api/users/1', {
name: 'Jane',
email: '[email protected]'
})
.then(response => console.log('User updated:', response))
.catch(error => console.error(error));
DELETE Request
// DELETE request
function deleteRequest(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('DELETE', url, true);
xhr.onload = function() {
if (xhr.status === 200 || xhr.status === 204) {
resolve('Deleted successfully');
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
};
xhr.onerror = () => reject(new Error('Network error'));
xhr.send();
});
}
// Usage
deleteRequest('/api/users/1')
.then(message => console.log(message))
.catch(error => console.error(error));
Request Headers and Configuration
Setting Request Headers
// Set custom headers
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/data', true);
// Set headers
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Authorization', 'Bearer token123');
xhr.setRequestHeader('X-Custom-Header', 'custom-value');
xhr.onload = function() {
console.log(xhr.responseText);
};
xhr.send(JSON.stringify({ data: 'value' }));
Getting Response Headers
// Get response headers
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data', true);
xhr.onload = function() {
// Get specific header
const contentType = xhr.getResponseHeader('Content-Type');
console.log('Content-Type:', contentType);
// Get all headers
const allHeaders = xhr.getAllResponseHeaders();
console.log('All headers:', allHeaders);
};
xhr.send();
Handling Different Response Types
JSON Response
// Handle JSON response
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users', true);
xhr.onload = function() {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log('Users:', data);
}
};
xhr.send();
XML Response
// Handle XML response
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data.xml', true);
xhr.onload = function() {
if (xhr.status === 200) {
const xmlDoc = xhr.responseXML;
const items = xmlDoc.getElementsByTagName('item');
for (let i = 0; i < items.length; i++) {
console.log(items[i].textContent);
}
}
};
xhr.send();
Text Response
// Handle text response
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/text', true);
xhr.onload = function() {
if (xhr.status === 200) {
const text = xhr.responseText;
console.log('Text:', text);
}
};
xhr.send();
Blob Response (for files)
// Handle binary response (file download)
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/download/file.pdf', true);
xhr.responseType = 'blob';
xhr.onload = function() {
if (xhr.status === 200) {
const blob = xhr.response;
const url = URL.createObjectURL(blob);
// Create download link
const link = document.createElement('a');
link.href = url;
link.download = 'file.pdf';
link.click();
}
};
xhr.send();
Progress Tracking
Upload Progress
// Track upload progress
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/upload', true);
// Track upload progress
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log(`Upload progress: ${percentComplete}%`);
// Update progress bar
const progressBar = document.getElementById('progress');
progressBar.value = percentComplete;
}
};
xhr.onload = function() {
console.log('Upload complete');
};
xhr.send(formData);
Download Progress
// Track download progress
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/large-file', true);
xhr.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log(`Download progress: ${percentComplete}%`);
}
};
xhr.onload = function() {
console.log('Download complete');
};
xhr.send();
Practical AJAX Patterns
Pattern 1: AJAX Form Submission
// Handle form submission with AJAX
document.getElementById('form').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/submit', true);
xhr.onload = function() {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
console.log('Form submitted:', response);
alert('Form submitted successfully!');
} else {
alert('Error submitting form');
}
};
xhr.onerror = function() {
alert('Network error');
};
xhr.send(formData);
});
Pattern 2: Auto-Save
// Auto-save content
let saveTimeout;
const textarea = document.getElementById('content');
textarea.addEventListener('input', function() {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/save', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status === 200) {
console.log('Content saved');
}
};
xhr.send(JSON.stringify({
content: textarea.value
}));
}, 1000); // Save after 1 second of inactivity
});
Pattern 3: Polling
// Poll server for updates
function pollForUpdates() {
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/updates', true);
xhr.onload = function() {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log('Updates:', data);
// Poll again after 5 seconds
setTimeout(pollForUpdates, 5000);
}
};
xhr.onerror = function() {
// Retry after 5 seconds on error
setTimeout(pollForUpdates, 5000);
};
xhr.send();
}
// Start polling
pollForUpdates();
Pattern 4: Request Timeout
// Implement timeout
function requestWithTimeout(url, timeout = 5000) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
// Set timeout
const timeoutId = setTimeout(() => {
xhr.abort();
reject(new Error('Request timeout'));
}, timeout);
xhr.onload = function() {
clearTimeout(timeoutId);
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
};
xhr.onerror = function() {
clearTimeout(timeoutId);
reject(new Error('Network error'));
};
xhr.send();
});
}
// Usage
requestWithTimeout('/api/data', 3000)
.then(data => console.log(data))
.catch(error => console.error(error));
AJAX with Authentication
// AJAX with authentication token
function authenticatedRequest(url, method = 'GET', data = null) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const token = localStorage.getItem('authToken');
xhr.open(method, url, true);
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status === 401) {
// Token expired, redirect to login
window.location.href = '/login';
reject(new Error('Unauthorized'));
} else if (xhr.status === 200 || xhr.status === 201) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
};
xhr.onerror = () => reject(new Error('Network error'));
xhr.send(data ? JSON.stringify(data) : null);
});
}
// Usage
authenticatedRequest('/api/protected', 'GET')
.then(data => console.log(data))
.catch(error => console.error(error));
Error Handling
Comprehensive Error Handling
// Comprehensive error handling
function robustRequest(url, options = {}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const method = options.method || 'GET';
const timeout = options.timeout || 5000;
xhr.open(method, url, true);
// Set headers
if (options.headers) {
Object.entries(options.headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
});
}
// Set timeout
const timeoutId = setTimeout(() => {
xhr.abort();
reject(new Error('Request timeout'));
}, timeout);
xhr.onload = function() {
clearTimeout(timeoutId);
try {
if (xhr.status >= 200 && xhr.status < 300) {
const data = xhr.responseText ? JSON.parse(xhr.responseText) : null;
resolve(data);
} else if (xhr.status === 404) {
reject(new Error('Resource not found'));
} else if (xhr.status === 401) {
reject(new Error('Unauthorized'));
} else if (xhr.status === 500) {
reject(new Error('Server error'));
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
} catch (error) {
reject(new Error('Invalid response format'));
}
};
xhr.onerror = function() {
clearTimeout(timeoutId);
reject(new Error('Network error'));
};
xhr.onabort = function() {
clearTimeout(timeoutId);
reject(new Error('Request aborted'));
};
xhr.send(options.data ? JSON.stringify(options.data) : null);
});
}
// Usage
robustRequest('/api/data', {
method: 'POST',
timeout: 3000,
headers: { 'Content-Type': 'application/json' },
data: { name: 'John' }
})
.then(data => console.log(data))
.catch(error => console.error(error.message));
XMLHttpRequest vs Fetch API
// XMLHttpRequest (older)
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data', true);
xhr.onload = () => console.log(xhr.responseText);
xhr.send();
// Fetch API (modern, recommended)
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));
// Fetch is cleaner and more modern, but XMLHttpRequest is still used in legacy code
Common Mistakes to Avoid
Mistake 1: Not Checking Status Code
// โ Wrong - Assumes success
xhr.onload = function() {
const data = JSON.parse(xhr.responseText);
console.log(data);
};
// โ
Correct - Check status
xhr.onload = function() {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
}
};
Mistake 2: Not Handling Errors
// โ Wrong - No error handling
xhr.open('GET', '/api/data', true);
xhr.send();
// โ
Correct - Handle errors
xhr.onerror = () => console.error('Request failed');
xhr.send();
Mistake 3: Synchronous Requests
// โ Wrong - Synchronous (blocks UI)
xhr.open('GET', '/api/data', false);
xhr.send();
// โ
Correct - Asynchronous
xhr.open('GET', '/api/data', true);
xhr.send();
Summary
XMLHttpRequest enables AJAX functionality:
- XMLHttpRequest is the foundation of AJAX
- Use
open()to configure requests - Handle
onloadandonerrorevents - Check
statuscode for success - Set headers with
setRequestHeader() - Support different response types
- Track progress with
onprogress - Always handle errors
- Consider using Fetch API for new code
- Implement timeouts for reliability
Related Resources
Next Steps
Continue your learning journey:
- Fetch API: Making HTTP Requests
- Error Handling with Promises and Async/Await
- Promise Utilities: all, race, allSettled, any
- Cookies: Creation, Reading, Deletion
Comments