When making AJAX requests from a mobile app, developers often need to distinguish between two critical scenarios:
- The server is actively responding but returning error codes (500-599)
- The server is completely unreachable (true downtime)
Contrary to some misconceptions, a completely offline server cannot return HTTP status codes. The 503 (Service Unavailable) scenario people mention only applies when:
- The web server process is running
- It's configured to return maintenance pages
- But the application layer is intentionally disabled
In true server downtime situations, you'll encounter:
// JavaScript fetch example
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
// Server responded with error status (4xx/5xx)
console.log('Server error:', response.status);
}
// Process successful response
} catch (error) {
// True connection failure scenarios:
if (error instanceof TypeError) {
// Network-level failure (DNS, CORS, etc.)
console.log('Server unreachable - network error');
} else {
// Other exceptions
console.log('Request failed:', error);
}
}
Implement this dual-check approach:
async function checkServerStatus(url) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch(url, {
signal: controller.signal,
method: 'HEAD' // Lightweight request
});
clearTimeout(timeoutId);
return {
status: 'live',
httpStatus: response.status
};
} catch (error) {
return {
status: 'down',
errorType: error.name // 'AbortError', 'TypeError', etc.
};
}
}
For production apps, consider these additional factors:
- DNS resolution failures throw different errors than TCP connection timeouts
- Load balancers might return 502/503 while the origin server is down
- Mobile networks have higher latency thresholds than desktop
Combine client-side detection with:
// Exponential backoff for retries
async function resilientRequest(url, retries = 3) {
try {
return await fetch(url);
} catch (error) {
if (retries === 0) throw error;
const delay = Math.pow(2, 4 - retries) * 1000;
await new Promise(res => setTimeout(res, delay));
return resilientRequest(url, retries - 1);
}
}
Remember that proper error handling requires understanding both HTTP semantics and network-layer failures. Always test with actual network disruption scenarios rather than just mock server errors.
When your mobile app makes AJAX requests, you'll encounter two fundamentally different error scenarios:
- Live server returning error codes (50X responses)
- Complete connection failure (no response at all)
Contrary to some misconceptions, a completely down server cannot return HTTP status codes. When the TCP stack isn't even responding, you'll get:
ECONNREFUSED
in Node.jsURLError
in Swift- Timeout exceptions in most languages
JavaScript detection pattern:
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
// Server is live but returned error (500-599)
console.log('Server error:', response.status);
}
} catch (error) {
if (error.message.includes('Failed to fetch')) {
// Complete connection failure
console.log('Server is likely down');
}
}
Android/Java equivalent:
try {
HttpURLConnection connection = (HttpURLConnection) new URL(API_URL).openConnection();
int status = connection.getResponseCode();
// Handle status codes here
} catch (ConnectException e) {
// Server unreachable
Log.e("NETWORK", "Server appears down: " + e.getMessage());
}
While documentation suggests using 503 for maintenance, this requires:
- A reverse proxy still running (e.g., Nginx)
- Application-aware load balancer
- Special maintenance mode infrastructure
True server crashes produce no HTTP response whatsoever.
For robust handling:
function checkServerStatus() {
return new Promise((resolve) => {
const timer = setTimeout(() => resolve('timeout'), 5000);
fetch(HEALTH_CHECK_ENDPOINT)
.then(res => {
clearTimeout(timer);
resolve(res.status === 200 ? 'healthy' : 'unhealthy');
})
.catch(() => {
clearTimeout(timer);
resolve('down');
});
});
}
Additional verification methods:
- Ping the server IP
- TCP port checks (netcat/telnet)
- DNS resolution validation