What Is the Timeout Event?
The timeout event is dispatched by XMLHttpRequest when an HTTP request fails to complete within the time specified by the timeout property. This event provides a clean mechanism for handling network requests that are taking too long, whether due to server unresponsiveness, network congestion, or connectivity issues.
When a timeout occurs, the XMLHttpRequest object automatically aborts the request and fires the timeout event on both the XMLHttpRequest instance (for download progress) and the XMLHttpRequestUpload instance (for upload progress). For related event handling patterns, see our guide on handling ClientHeight for understanding DOM measurement events.
The timeout event is part of a broader event model that includes loadstart, progress, load, loadend, error, abort, and timeout. Understanding where the timeout event fits into this lifecycle helps developers implement comprehensive request handling that accounts for all possible outcomes. When a timeout fires, it serves as a terminal event--just like load, error, or abort--meaning no further events will be dispatched for that request. This predictable behavior allows you to structure your event handlers with confidence, knowing that once the timeout event fires, your cleanup and recovery code will execute without race conditions.
Event Type and Properties
The timeout event is a ProgressEvent that inherits from the base Event interface. This means it includes all standard event properties while also providing access to progress-related attributes. For the timeout event specifically, the lengthComputable property is typically false because timeout scenarios often occur before meaningful progress data can be calculated.
The event inherits from ProgressEvent through this inheritance chain: Event → ProgressEvent → XMLHttpRequestEventTarget. This inheritance provides access to properties like loaded and total, though these may have limited utility in timeout scenarios where the request may not have made significant progress before being terminated. Understanding this inheritance helps when debugging timeout issues, as you can access the same event methods and properties you use for other XHR events. For more context on ProgressEvent behavior, see our article on RequestVideoFrameCallback which also uses progress events.
The event inherits properties from the MDN Web Docs specification, providing consistent behavior across all modern browsers.
Setting the Timeout Property
The XMLHttpRequest.timeout property is an unsigned long integer representing the number of milliseconds a request can take before automatically being terminated. The default value is 0, which means there is no timeout by default.
Default Behavior
With the default timeout of 0, requests will wait indefinitely for a response. This can lead to frozen interfaces and poor user experience when network issues occur. The browser will maintain the connection open indefinitely, waiting for a server response that may never come--whether due to server crashes, network partitions, or simply extremely slow responses. In production applications, relying on the default timeout is a critical oversight that can leave users staring at loading spinners with no indication of what went wrong or how to recover. For understanding memory implications of long-running requests, see our guide on Int8Array buffer handling.
Configuration Examples
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.timeout = 5000; // 5 second timeout
When the timeout value is set, the browser begins counting from the moment the request starts (when send() is called). If the request hasn't completed within the specified milliseconds, the browser terminates the request and fires the timeout event. For additional context on buffer management during request handling, review our Buffer documentation.
As documented in the MDN Web Docs, timeout values should be configured based on expected request duration and network conditions.
1const xhr = new XMLHttpRequest();2xhr.open('GET', '/api/data', true);3xhr.timeout = 5000; // 5 seconds4 5xhr.onload = function() {6 if (xhr.status === 200) {7 console.log('Success');8 }9};10 11xhr.ontimeout = function(e) {12 console.error('Timeout!');13};14 15xhr.send();Choosing Appropriate Timeout Values
Selecting appropriate timeout values requires balancing several factors:
- Request type: Quick API validation calls need shorter timeouts (3-5 seconds)
- File uploads: Large file transfers need longer timeouts (30-60+ seconds)
- Network conditions: Mobile users may need longer timeouts
- User experience: Timeouts should align with user expectations
Adaptive timeout strategies can dynamically adjust based on detected network conditions. Modern browsers support the Network Information API, which provides information about the user's connection type. Applications can use this to set shorter timeouts for users on fast connections while allowing more patience for those on mobile networks. Additionally, consider implementing progressive timeouts--starting with a reasonable default but automatically extending the window if you detect the server is responding, just slowly.
Some production systems implement tiered timeout approaches where the first request attempt uses a standard timeout, but subsequent retries use progressively longer windows. This exponential backoff pattern prevents server overload while still giving legitimate slow requests a chance to complete. The key is balancing user expectations: users generally accept waiting longer for complex operations than for simple data fetches.
| Request Type | Recommended Timeout | Rationale |
|---|---|---|
| Quick API validation | 3-5 seconds | Fast user feedback |
| Standard data fetch | 5-10 seconds | Balance responsiveness |
| Complex queries | 10-20 seconds | Server processing time |
| File upload | 30-60+ seconds | File size dependent |
Handling the Timeout Event
Using addEventListener
The preferred method for handling timeout events is addEventListener, which allows multiple handlers for the same event:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.timeout = 5000;
xhr.addEventListener('timeout', (event) => {
console.error('Request timed out');
// Handle timeout - show message, disable loading state
});
xhr.addEventListener('load', (event) => {
if (xhr.status >= 200 && xhr.status < 300) {
// Success handling
} else {
// HTTP error handling
}
});
xhr.addEventListener('error', (event) => {
console.error('Network error occurred');
});
xhr.send();
Using Handler Properties
For simpler use cases, you can use the ontimeout handler property:
xhr.ontimeout = function(event) {
console.error('Request timed out after 5 seconds');
};
The choice between addEventListener and handler properties depends on your use case. addEventListener is the modern, preferred approach because it allows multiple handlers for the same event type--useful when multiple modules need to respond to timeout conditions. It also provides cleaner separation of concerns in larger applications. Handler properties like ontimeout are simpler for small scripts or when you only need a single handler. They also offer slight performance benefits in scenarios with many rapid requests, though this difference is rarely significant in practice. For production applications, we recommend addEventListener for its flexibility and maintainability. Related event handling patterns are covered in our TransitionEvent guide.
As documented by MDN Web Docs, both approaches are valid but serve different architectural needs.
Timeout Handling for Uploads
XMLHttpRequest supports upload progress monitoring, and timeouts apply to uploads as well. The timeout event can be listened to on the xhr.upload object.
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/upload', true);
xhr.timeout = 60000; // 60 seconds for upload
// Monitor upload progress
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log(`Upload: ${percentComplete.toFixed(2)}%`);
}
});
// Handle upload timeout
xhr.upload.addEventListener('timeout', (event) => {
console.error('Upload timed out');
});
xhr.send(formData);
Upload timeouts are critical for applications handling large files. Unlike quick API calls, file uploads can take minutes depending on file size and connection speed. Without proper timeout handling, users uploading large media files might stare at a progress bar indefinitely if network issues occur mid-transfer. Implementing upload timeouts along with progress monitoring gives users clear feedback about upload status and prevents application state from becoming stale. Consider providing visual countdown warnings when uploads approach timeout thresholds, giving users the option to cancel and retry before failure. For styling progress indicators, see our guide on Creating Realistic Reflections with CSS.
The upload timeout event behavior is consistent with the MDN Web Docs specification for XMLHttpRequestEventTarget events.
Progress Monitoring
Track upload progress and show users a progress bar during file uploads
Longer Timeouts
File uploads require significantly longer timeouts based on file size and connection speed
Resume Support
Consider implementing chunked uploads with resume capability for large files
Clear Feedback
Show upload status and timeout warnings to users before failure
Best Practices for Timeout Implementation
Always Implement Error Handling
Never assume network requests will succeed. Always implement handlers for timeout, error, and HTTP error status codes. A comprehensive error handling pattern wraps XMLHttpRequest in a Promise, handling all terminal events and providing consistent error responses:
async function safeRequest(url, options = {}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(options.method || 'GET', url, true);
xhr.timeout = options.timeout || 5000;
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({ data: xhr.response, status: xhr.status });
} else {
reject(new Error(`HTTP Error: ${xhr.status}`));
}
});
xhr.addEventListener('timeout', () => {
reject(new Error('Request timed out'));
});
xhr.addEventListener('error', () => {
reject(new Error('Network error'));
});
xhr.addEventListener('abort', () => {
reject(new Error('Request aborted'));
});
if (options.body) {
xhr.send(options.body);
} else {
xhr.send();
}
});
}
Provide User Feedback
When timeouts occur, users should receive clear feedback about what happened:
xhr.addEventListener('timeout', () => {
loadingIndicator.hide();
showNotification('The request took too long. Please try again.', 'warning');
if (confirm('Would you like to retry the request?')) {
retryRequest();
}
});
Implement Retry Logic
For non-critical failures, implementing exponential backoff retry logic can improve reliability. The key decision is whether to retry or fail-fast: retry for transient network issues or server overload, fail-fast for client errors or timeout on non-idempotent operations:
function requestWithRetry(url, options = {}, maxRetries = 3) {
return new Promise((resolve, reject) => {
let attempts = 0;
function attempt() {
const xhr = new XMLHttpRequest();
xhr.open(options.method || 'GET', url, true);
xhr.timeout = options.timeout || 5000;
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else if (attempts < maxRetries) {
attempts++;
const delay = Math.pow(2, attempts) * 1000;
setTimeout(attempt, delay);
} else {
reject(new Error(`Failed after ${maxRetries} attempts`));
}
});
xhr.addEventListener('timeout', () => {
if (attempts < maxRetries) {
attempts++;
const delay = Math.pow(2, attempts) * 1000;
setTimeout(attempt, delay);
} else {
reject(new Error('Request timed out'));
}
});
xhr.addEventListener('error', () => {
if (attempts < maxRetries) {
attempts++;
const delay = Math.pow(2, attempts) * 1000;
setTimeout(attempt, delay);
} else {
reject(new Error('Network error'));
}
});
xhr.send(options.body || null);
}
attempt();
});
}
Separate Concerns
Organize event handlers to clearly differentiate between timeout, error, and HTTP status errors. Each failure type has different causes and should provide distinct user feedback. Timeouts suggest the server is slow or unreachable--show patience-inspiring messages. Network errors suggest connectivity issues--prompt users to check their connection. HTTP errors indicate the server responded but with a problem--provide specific error information from the response. For additional styling guidance relevant to error states, see our article on CSS Text Decoration.
1function safeRequest(url, options = {}) {2 return new Promise((resolve, reject) => {3 const xhr = new XMLHttpRequest();4 xhr.open(options.method || 'GET', url, true);5 xhr.timeout = options.timeout || 5000;6 7 xhr.addEventListener('load', () => {8 if (xhr.status >= 200 && xhr.status < 300) {9 resolve({ data: xhr.response, status: xhr.status });10 } else {11 reject(new Error(`HTTP Error: ${xhr.status}`));12 }13 });14 15 xhr.addEventListener('timeout', () => {16 reject(new Error('Request timed out'));17 });18 19 xhr.addEventListener('error', () => {20 reject(new Error('Network error'));21 });22 23 xhr.addEventListener('abort', () => {24 reject(new Error('Request aborted'));25 });26 27 xhr.send(options.body || null);28 });29}30 31// Usage32safeRequest('/api/data', { timeout: 3000 })33 .then(result => console.log('Success:', result))34 .catch(error => {35 if (error.message.includes('timed out')) {36 console.log('Retry or show timeout message');37 } else {38 console.log('Other error:', error.message);39 }40 });Performance Considerations
Avoiding Synchronous Requests
Synchronous XMLHttpRequests are deprecated and should never be used in production code. Additionally, the timeout property cannot be used with synchronous requests in document environments.
Request Batching and Cancellation
For applications that make many requests, implement request cancellation to prevent unnecessary network traffic and memory leaks. A request manager pattern helps track and cancel pending requests:
class RequestManager {
constructor() {
this.pendingRequests = new Map();
}
makeRequest(id, url, options = {}) {
if (this.pendingRequests.has(id)) {
this.pendingRequests.get(id).abort();
}
const xhr = new XMLHttpRequest();
xhr.open(options.method || 'GET', url, true);
xhr.timeout = options.timeout || 5000;
const cleanup = () => {
this.pendingRequests.delete(id);
};
xhr.addEventListener('load', cleanup);
xhr.addEventListener('timeout', cleanup);
xhr.addEventListener('error', cleanup);
xhr.addEventListener('abort', cleanup);
this.pendingRequests.set(id, xhr);
xhr.send(options.body || null);
return xhr;
}
cancelRequest(id) {
if (this.pendingRequests.has(id)) {
this.pendingRequests.get(id).abort();
this.pendingRequests.delete(id);
}
}
}
Memory Management
Ensure proper cleanup of XMLHttpRequest objects to prevent memory leaks. When users navigate away from pages with pending requests, those XHR objects should be properly disposed. Use AbortController patterns and cleanup on page unload events:
function fetchData(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.timeout = 5000;
const abortController = new AbortController();
xhr.addEventListener('abort', () => {
reject(new Error('Request cancelled'));
});
const cleanup = () => {
xhr.abort();
};
window.addEventListener('unload', cleanup);
xhr.addEventListener('load', () => {
window.removeEventListener('unload', cleanup);
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
});
xhr.addEventListener('timeout', () => {
window.removeEventListener('unload', cleanup);
reject(new Error('Timeout'));
});
xhr.addEventListener('error', () => {
window.removeEventListener('unload', cleanup);
reject(new Error('Network error'));
});
xhr.send();
return () => {
window.removeEventListener('unload', cleanup);
xhr.abort();
};
});
}
As covered in High Performance Browser Networking, proper request management and cleanup are essential for maintaining application performance under load.
Common Pitfalls and Solutions
Pitfall 1: No Timeout Set
The default timeout of 0 means requests wait indefinitely.
// Bad - no timeout
const xhr = new XMLHttpRequest();
// Good - reasonable timeout
const xhr = new XMLHttpRequest();
xhr.timeout = 5000;
Pitfall 2: Treating Timeout as Success
Ensure your timeout handler prevents success logic from running. Use completion flags to prevent race conditions where both timeout and load might fire:
// Good - track completion state
let completed = false;
xhr.addEventListener('load', () => {
completed = true;
processResponse();
});
xhr.addEventListener('timeout', () => {
completed = true;
handleTimeout();
});
Pitfall 3: Not Distinguishing Between Error Types
Generic error handling loses important context. Specific handling provides better user experience by differentiating between network failures and slow responses:
// Generic error handling loses important context
xhr.addEventListener('error', () => {
showMessage('Something went wrong'); // Too vague
});
// Specific handling provides better UX
xhr.addEventListener('timeout', () => {
showMessage('The server is taking too long. Please try again.');
});
xhr.addEventListener('error', () => {
showMessage('Unable to connect to the server. Check your connection.');
});
Pitfall 4: Long-Running Polling Without Timeout
When implementing polling, each individual request needs a timeout even if the overall polling continues. This ensures one stuck request doesn't halt the entire polling mechanism:
// Good - each poll request has timeout
async function pollForUpdates() {
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/updates', true);
xhr.timeout = 3000;
return new Promise((resolve, reject) => {
xhr.addEventListener('load', () => resolve(JSON.parse(xhr.responseText)));
xhr.addEventListener('timeout', () => resolve(null)); // Continue polling
xhr.addEventListener('error', () => resolve(null)); // Continue polling
xhr.send();
});
}
Timeout Event Relationship to Other Events
The timeout event fits into the XMLHttpRequest lifecycle:
- loadstart: Fires when request begins
- progress: Fires periodically during data transfer
- timeout: Fires if timeout expires before completion
- load: Fires on successful completion
- error: Fires on network failure
- abort: Fires when request is cancelled
- loadend: Always fires after terminal events
loadstart → [progress...] → { load | timeout | error | abort } → loadend
↑
Can fire multiple times
during data transfer
Only one of load, timeout, error, or abort will fire for any given request. After any of these terminal events fires, no further events will be dispatched for that request except loadend, which always fires last. This predictable lifecycle allows developers to structure cleanup and error handling with confidence. The loadend event serves as a reliable hook for cleanup operations that should run regardless of how the request ended--whether successfully, timed out, failed with an error, or was deliberately cancelled.
xhr.addEventListener('loadstart', () => console.log('Request started'));
xhr.addEventListener('progress', () => console.log('In progress'));
xhr.addEventListener('timeout', () => console.log('Timed out'));
xhr.addEventListener('load', () => console.log('Loaded'));
xhr.addEventListener('error', () => console.log('Error'));
xhr.addEventListener('abort', () => console.log('Aborted'));
xhr.addEventListener('loadend', () => console.log('Finished (terminal event fired)'));
Modern Alternatives: Fetch API
While XMLHttpRequest remains widely supported, the Fetch API provides a more modern alternative. Fetch doesn't have a built-in timeout property but supports AbortController for cancellation:
const controller = new AbortController();
const signal = controller.signal;
fetch('/api/data', { signal })
.then(response => response.json())
.catch(error => {
if (error.name === 'AbortError') {
console.log('Request was aborted');
}
});
// Timeout implementation
setTimeout(() => controller.abort(), 5000);
When choosing between XMLHttpRequest and Fetch, consider your requirements: Use XHR for maximum browser compatibility, upload progress monitoring, or when you need the built-in timeout property. Use Fetch for new projects where you can rely on modern browsers, and prefer its cleaner Promise-based API. Migration from XHR to Fetch typically involves creating wrapper functions that translate between the callback-based XHR patterns and Fetch's Promise approach. Many teams maintain XHR utilities for legacy support while building new features with Fetch. For CSS styling approaches relevant to loading states, see our article on CSS Contain Property.
Key Takeaway: XMLHttpRequest remains essential for supporting older browsers and understanding legacy codebase patterns. Master its timeout handling for robust applications.
| Feature | XMLHttpRequest | Fetch API |
|---|---|---|
| Built-in timeout property | Yes | No (use AbortController) |
| Event-based | Yes | Promise-based |
| Browser support | Universal | Modern browsers |
| Upload progress | Yes | Limited |
| Synchronous mode | Yes (deprecated) | No |
Frequently Asked Questions
What is the default timeout value for XMLHttpRequest?
The default timeout value is 0, which means there is no timeout and requests will wait indefinitely. You must explicitly set a timeout value to enable timeout behavior.
Can I use timeout with synchronous XMLHttpRequest?
No, timeout cannot be used with synchronous requests in a document environment. Attempting to do so will throw an InvalidAccessError exception.
What happens when a timeout occurs?
When a timeout occurs, the browser automatically aborts the request and fires the timeout event. The request is terminated and no further events will be dispatched.
How is the timeout event different from the error event?
The timeout event fires specifically when the timeout expires before the request completes. The error event fires when there's a network-level failure. They require different handling strategies.
How do I handle timeouts for file uploads?
Listen for the timeout event on xhr.upload in addition to the main xhr object. File uploads typically require longer timeouts (60+ seconds) depending on file size.
Conclusion
Implementing proper timeout handling with the XMLHttpRequest timeout event is essential for building reliable web applications. Remember these key principles:
- Always set explicit timeouts (never rely on the default of no timeout)
- Distinguish between timeout, error, and HTTP status failures in your handling logic
- Provide meaningful feedback to users when timeouts occur
- Implement retry logic with exponential backoff for non-critical requests
- Properly clean up XMLHttpRequest objects to prevent memory leaks
By mastering timeout handling, you ensure your applications remain responsive and predictable even when network conditions are less than ideal. Our web development services can help you implement robust network request handling across your applications, while our performance optimization expertise ensures your applications stay fast and reliable under varying network conditions.