A loading button reports success even though the server returned a 404 page. The code contains try/catch, but the expected error message never appears. This does not mean JavaScript ignores exceptions: fetch distinguishes a received HTTP response from failure of the request itself. Fetch error handling requires an explicit status check, not just a catch block around the network call.
We will examine a learning page that reads a small JSON file from the same local server. This is not a client case or an integration with payments or enquiries. You need a modern browser, an editor and local Python 3 for a simple test server. No packages or administrator permissions are needed. Use a separate folder containing only learning files: do not start the server in a directory containing secrets, backups or genuine website data.
A server response does not mean a successful operation
Open the browser’s Network tab and repeat the problematic action once. Inspect the request address, HTTP status and response body. If the server returns 404 or 500, a response arrived, but the requested resource was not supplied successfully. According to the Fetch API documentation, that status alone does not reject the fetch promise. Check response.ok separately before using the data.
A different situation occurs when the request does not finish with an accessible response, for example because of a network problem. Fetch may then reject its promise. Invalid JSON is another separate stage: a response can have a successful HTTP status but be impossible to parse. Do not label every entry into catch as “server unavailable”. Diagnosis needs the stage, status and safe technical context rather than a single universal explanation.
Start with a small reproducible example. Create index.html in the test folder using the markup below. The load and status identifiers are illustrative, but must match the JavaScript selectors. The defer attribute runs the linked script after the document has been parsed. The learning interface’s labels remain in English so that identical code can be compared conveniently between the article’s language versions.
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Fetch test</title>
<button id="load" type="button">Load data</button>
<p id="status" role="status"></p>
<script src="app.js" defer></script>
</html>
Check the status, data and button state
Create app.js beside it and insert the code. The ./data.json address is relative to the page directory; replace it only with an agreed test resource. The example performs GET and does not submit a form. It adds no authentication, changes no CORS rules and does not bypass server protections. Before moving it into a real application, preserve the component’s previous version and check changes on a test copy.
const button = document.querySelector("#load");
const status = document.querySelector("#status");
button.addEventListener("click", async () => {
button.disabled = true;
status.textContent = "Loading...";
try {
const response = await fetch("./data.json");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (typeof data?.message !== "string") {
throw new Error("Unexpected response shape");
}
status.textContent = data.message;
} catch (error) {
status.textContent = "Could not load data";
console.warn(error instanceof Error ? error.message : "Request failed");
} finally {
button.disabled = false;
}
});
There are three successive checks: an available HTTP result, valid JSON and a string message field. response.ok covers successful HTTP statuses, but does not establish correct business data. A type check is sufficient for this learning schema; a real catalogue or profile needs its own contract. For example, a successful response with an error field must not become a completed-payment message solely because its status is 200.
Text is displayed through textContent rather than interpreted as HTML. The button is unavailable during the request and becomes available again in finally. This reduces accidental repeated clicks in the example, but is not server-side duplicate protection. Do not extend that guarantee to orders, messages or payments. There are no automatic retries here: repeating a dangerous operation requires separate design.
Create a third file, data.json, with the following content. The field and text are fictional and intended only for testing. In the terminal, enter the learning folder itself and run the command after the JSON example. The server binds to a local address, not a public interface. It temporarily makes files in that folder readable over HTTP; do not use it as a production server or expose it to the network.
{"message":"Demo data loaded"}
python3 -m http.server 8099 --bind 127.0.0.1
Open http://127.0.0.1:8099/index.html and press Load data. The expected text is Demo data loaded. This is the learning scenario’s expectation, not a measured result from your website. If the port is occupied, choose another available local port and adjust the browser address accordingly. To finish, press Ctrl+C in the server terminal. The Python documentation describes the startup parameters.
Demonstrate that the intended failure is handled
First temporarily change the address in app.js to ./missing-demo.json, ensuring that file does not exist in the folder. Reload the page and press the button again. Expect 404 in Network, Could not load data on the page and HTTP 404 in the console. Our response.ok check creates the exception for this case. If the local environment returns another status, investigate it rather than forcing the conclusion to match the example.
Restore the address and deliberately invalidate the JSON, for example by removing the closing brace. Check the user message and console again. The HTTP status may be 200, but parsing should fail. Then restore valid JSON and replace message with a number: this tests the data shape instead. After each exercise, undo the particular change so that the next test is not masked by the previous failure.
For a real website, separately test slow responses, request cancellation and behaviour after navigating elsewhere: this minimal example implements none of them. Do not show visitors the complete response body or internal stack. If the problem needs to reach a developer, prepare a short bug report with the scenario and status, without secrets. Conclusion: check every stage separately. AriZone can help align interface messages with the actual API outcome and verify the fix in a test environment.
Leave a comment