Summary
During an investigation of intermittent failures when calling a REST endpoint with fetch(), the root cause was that the server sometimes returned non‑JSON content types (e.g., text/html, text/plain) due to server‑side errors. The client, always calling .json(), attempted to parse these responses and threw a SyntaxError.
Root Cause
- Server‑side intermittent errors
- The API sometimes responds with an HTML error page instead of JSON.
- The
Content-Typeheader is not guaranteed to beapplication/jsonfor error responses.
- Client assumptions
- The code always executes
response.json()without checkingresponse.okor theContent-Type. - Unchecked
Promiserejections propagate as uncaught errors.
- The code always executes
Why This Happens in Real Systems
- Load balancers and micro‑service orchestrators may return fallback pages if downstream services timeout or crash.
- API gateways can emit policy‑related error messages that are HTML or plain text.
- Network hiccups can trigger incomplete responses that the browser interprets as HTML.
Real-World Impact
- Unreliable UX: Users see errors sporadically with no explanation.
- Poor debugging: Stack traces point to
SyntaxError: Unexpected token <or similar, hiding the true cause. - Increased support tickets: Clients believe the API is down, while the real issue is in error handling logic.
Example or Code (if necessary and relevant)
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
return response.text().then(text => Promise.reject(new Error(text)));
}
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return response.json();
}
return Promise.reject(new Error('Expected application/json but got ' + contentType));
})
.then(data => {
console.log('Data received:', data);
})
.catch(error => {
console.error('Error:', error.message);
});
How Senior Engineers Fix It
- Validate the response
- Check
response.okto detect HTTP errors before parsing. - Inspect the
Content‑Typeheader and only parse if it containsapplication/json.
- Check
- Graceful fallbacks
- Provide default structures or user‑friendly error messages when parsing fails.
- Log detailed diagnostics (status code, headers, raw body) to aid ops.
- Automated tests
- Mock error responses with different content types to ensure the client behaves correctly.
- Include edge cases like broken JSON, empty responses, and large payloads.
- Infrastructure monitoring
- Gateways: ensure proper fault‑tolerant handling or redirection to JSON responses.
- Alert on unexpected content types or high error rates.
Why Juniors Miss It
- They assume
fetch()always receives JSON when the status code is 200. - They overlook HTTP error status codes and
okproperty. - They ignore the importance of
Content-Typevalidation. - They lack experience handling promises and rejections cleanly.
By following the practices above, you eliminate the ambiguity and ensure robust, predictable data fetching in production.