Table of Contents#
- Understanding
net::ERR_CONNECTION_REFUSED - How XMLHttpRequest Works: A Quick Refresher
- Step-by-Step Guide to Catching
net::ERR_CONNECTION_REFUSED - Advanced Handling Techniques
- Best Practices for Robust Error Handling
- Conclusion
- References
1. Understanding net::ERR_CONNECTION_REFUSED#
What Causes It?#
net::ERR_CONNECTION_REFUSED is a low-level network error indicating the client’s attempt to connect to a server failed. Common triggers include:
- The server is not running or has crashed.
- The server is running but not listening on the requested port (e.g., trying to connect to port 3000 when the server uses 8080).
- Firewall rules block the client’s IP or the target port.
- Network issues (e.g., no internet, DNS failure, or a misconfigured proxy).
How It Differs from Other Errors#
It’s important to distinguish net::ERR_CONNECTION_REFUSED from other network errors:
- 404 Not Found: The server is reachable, but the requested resource doesn’t exist.
- 500 Internal Server Error: The server is reachable but encountered an error processing the request.
- Timeout: The server took too long to respond (different from a refused connection).
net::ERR_CONNECTION_REFUSEDspecifically means the client could not establish a TCP connection to the server.
2. How XMLHttpRequest Works: A Quick Refresher#
Before diving into error handling, let’s recap how XMLHttpRequest (XHR) works. XHR is an API for making asynchronous HTTP/HTTPS requests to a server. A typical workflow involves:
- Creating an XHR object:
const xhr = new XMLHttpRequest(); - Configuring the request:
xhr.open(method, url, async);(async=true for asynchronous requests). - Setting up event listeners: To handle success, errors, progress, or timeouts.
- Sending the request:
xhr.send(body);
XHR relies on event-driven callbacks to handle asynchronous responses. Key events include:
onload: Triggered when the request completes successfully.onerror: Triggered when a network error occurs (e.g., no internet, connection refused).ontimeout: Triggered if the request exceeds thetimeoutthreshold.onreadystatechange: Triggered when the request’sreadyStatechanges (e.g., from "loading" to "done").
3. Step-by-Step Guide to Catching net::ERR_CONNECTION_REFUSED#
Catching net::ERR_CONNECTION_REFUSED in XHR requires listening to network error events and interpreting subtle clues from the XHR object. Here’s how to do it:
Step 1: Listen for the onerror Event#
Network errors (including net::ERR_CONNECTION_REFUSED) trigger the onerror event. Unlike HTTP errors (e.g., 404, 500), which fire onload with a non-2xx status, onerror is exclusive to failures in the network layer.
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true); // Asynchronous request
// Handle network errors (including connection refused)
xhr.onerror = function (event) {
console.error('Network error occurred:', event);
// We’ll expand this to detect connection refused next
};
xhr.send();Step 2: Identify net::ERR_CONNECTION_REFUSED Using status#
When net::ERR_CONNECTION_REFUSED occurs, the server never receives the request, so no HTTP status code (e.g., 200, 404) is returned. In most browsers, XHR sets xhr.status to 0 for such cases.
Thus, combining onerror with xhr.status === 0 is a reliable way to detect connection refused (and other network failures like no internet).
xhr.onerror = function (event) {
if (xhr.status === 0) {
// Status 0 indicates no HTTP response (network error)
console.error('net::ERR_CONNECTION_REFUSED or network failure detected');
handleConnectionRefusedError(); // Custom handler
} else {
console.error('Other network error:', xhr.statusText);
}
};Step 3: Validate with readyState (Optional)#
The readyState property indicates the request’s lifecycle. For completed requests (successful or failed), readyState will be 4 (DONE). You can use this to confirm the request finished processing:
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) { // Request completed
if (xhr.status === 0 && xhr.onerror) {
// Connection refused or network error
handleConnectionRefusedError();
} else if (xhr.status >= 200 && xhr.status < 300) {
// Success: process response
const data = JSON.parse(xhr.responseText);
} else {
// HTTP error (e.g., 404, 500)
console.error('HTTP Error:', xhr.status, xhr.statusText);
}
}
};Step 4: Handle Timeouts Separately#
A timeout (e.g., server is slow to respond) is distinct from net::ERR_CONNECTION_REFUSED. Use ontimeout to handle timeouts explicitly:
xhr.timeout = 5000; // 5-second timeout
xhr.ontimeout = function () {
console.error('Request timed out after 5 seconds');
handleTimeoutError();
};4. Advanced Handling Techniques#
Catching the error is just the first step. To build resilient apps, you’ll need to recover gracefully, notify users, and diagnose issues. Here are advanced strategies:
Retries with Exponential Backoff#
Temporary network glitches or server restarts may resolve quickly. Retrying failed requests (with limits) can improve success rates. Use exponential backoff to avoid overwhelming the server:
function fetchWithRetry(url, retries = 3, delay = 1000) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onerror = function () {
if (xhr.status === 0 && retries > 0) {
console.log(`Retrying (${retries} left)...`);
// Wait (delay) ms before retrying; double delay each attempt
setTimeout(() => fetchWithRetry(url, retries - 1, delay * 2), delay);
} else {
handleConnectionRefusedError(); // No retries left
}
};
xhr.send();
}
// Usage: Retry up to 3 times with initial 1-second delay
fetchWithRetry('https://api.example.com/data', 3, 1000);Note: Only retry idempotent requests (e.g., GET, HEAD). Avoid retrying POST/PUT unless you’re sure they won’t cause side effects (e.g., duplicate payments).
User-Friendly Notifications#
Replace technical jargon with clear messages to keep users informed:
function handleConnectionRefusedError() {
const errorDiv = document.getElementById('error-message');
errorDiv.textContent = 'Oops! Could not connect to the server. Please check your internet or try again later.';
errorDiv.style.color = 'red';
}Log Errors for Debugging#
Send error details to a backend service (e.g., Sentry, Datadog) for monitoring:
function logErrorToBackend(errorDetails) {
const logXhr = new XMLHttpRequest();
logXhr.open('POST', 'https://your-logging-service.com/errors', true);
logXhr.setRequestHeader('Content-Type', 'application/json');
logXhr.send(JSON.stringify(errorDetails));
}
// In your onerror handler:
xhr.onerror = function () {
if (xhr.status === 0) {
const errorDetails = {
timestamp: new Date().toISOString(),
url: xhr.responseURL,
error: 'net::ERR_CONNECTION_REFUSED',
userAgent: navigator.userAgent
};
logErrorToBackend(errorDetails);
handleConnectionRefusedError();
}
};Fallback Content#
If the request fails, load cached data or static fallback content to keep the app usable:
xhr.onerror = function () {
if (xhr.status === 0) {
const cachedData = localStorage.getItem('cachedData');
if (cachedData) {
console.log('Using cached data as fallback');
renderData(JSON.parse(cachedData));
} else {
handleConnectionRefusedError();
}
}
};5. Best Practices for Robust Error Handling#
To avoid pitfalls and ensure your error handling is effective, follow these best practices:
1. Never Ignore Errors#
Silent failures (e.g., unhandled onerror) leave users confused. Always log errors and notify users.
2. Differentiate Between Error Types#
Not all status: 0 errors are net::ERR_CONNECTION_REFUSED. Use additional context (e.g., navigator.onLine to check if the user is offline) to refine detection:
if (xhr.status === 0) {
if (!navigator.onLine) {
console.error('User is offline');
} else {
console.error('net::ERR_CONNECTION_REFUSED (server unreachable)');
}
}3. Limit Retries#
Excessive retries can overload servers or drain user bandwidth. Stick to 2–3 retries with exponential backoff.
4. Handle CORS Preflight Errors#
Cross-Origin Resource Sharing (CORS) preflight requests ( OPTIONS method) may fail with net::ERR_CONNECTION_REFUSED if the server isn’t configured to handle CORS. Test preflight behavior with tools like browser DevTools (Network tab).
5. Test Error Scenarios#
Simulate net::ERR_CONNECTION_REFUSED during development by:
- Stopping the server.
- Using a non-existent port (e.g.,
http://localhost:9999when the server runs on3000). - Blocking the request with browser DevTools (Network > Block request URL).
6. Consider Modern Alternatives#
While XHR is still used in legacy code, modern apps often use the fetch API or libraries like axios, which simplify error handling. For example, fetch rejects promises on network errors, making it easier to catch with try/catch:
// fetch example (for comparison)
fetch('https://api.example.com/data')
.catch(error => {
if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
console.error('net::ERR_CONNECTION_REFUSED or network error');
}
});6. Conclusion#
Handling net::ERR_CONNECTION_REFUSED in XMLHttpRequest is critical for building reliable web applications. By listening to the onerror event, checking for xhr.status === 0, and combining this with retries, user notifications, and logging, you can ensure users stay informed and your app remains resilient.
Remember: Network errors are inevitable, but poor error handling is not. With the techniques outlined here, you’ll turn frustrating failures into opportunities to improve user trust and app stability.