Original guides / Guide
Debug async/await and failed fetch requests
Separate transport errors, HTTP failures and invalid response bodies with a reproducible fixture.
You will learn to
- Check response.ok
- Preserve useful failure context
- Await work at the right boundary
Before you start
JavaScript promises
A resolved promise is not HTTP success
fetch resolves when a response arrives, including status 404 or 500. A transport failure typically rejects instead. A catch block alone is therefore not a status check. Inspect response.ok before treating the body as application data.
A login URL returning SPA HTML can make response.json throw. The UI might label that a network error even though transport succeeded. Inspect status and Content-Type before changing credentials. Never log passwords, authorization headers or personal records while investigating.
Follow the promise chain
await pauses the current async function, not every event on the page. Returning a promise lets the caller wait for completion. Forgetting return in a wrapper can make the outer operation finish too early, leaving undefined data or a loading flag that clears before the result.
Put loading cleanup in finally so both success and failure release it. Show useful errors without exposing internal SQL or private stack traces. Distinguish a service outage from invalid credentials; otherwise users repeatedly change a password that was never checked.
Bound and cancel work
Use AbortController to cancel obsolete requests. A timeout means the operation took too long, not necessarily that the server rejected it. When searches overlap, an older result must not overwrite the latest query. A request identifier can help distinguish them.
Retries can duplicate side effects. Retry a safe read more freely than a payment or enrollment request. State changes need idempotency supported by the server. Disabling a browser button does not create that guarantee.
Run the fixture
This exercise uses local response objects, not a production API. Implement readResult so it throws an Error containing the status for unsuccessful responses and awaits json otherwise. Checks cover success and HTTP failure separately.
After it passes, make the fixture’s json function throw a syntax error and inspect the output. Remove an await and reason about timing. The expected result is parsed data on success and an explained error on failure, with no real accounts involved.
Reproduce each failure separately
Change only one response property at a time. First return ok false with status 404; the result should identify an HTTP failure before treating the body as success data. Next make the body parser throw; that is a format problem. Finally make the request promise reject, which follows the transport-failure path. Logging status and content type is often more useful than repeatedly changing credentials.
This exercise deliberately uses a response fixture rather than a live endpoint. It can reproduce your control flow without leaking credentials or relying on a third-party server. After the tests pass, wire the same checks into a real fetch call in your own application and verify the request in the browser network panel. Never log authorization headers or full private response bodies.
Try it yourself
async function readResult(response) {
return response.json();
}
console.log(await readResult({ok:true, status:200, json:async()=>({message:"hello"})}));Enable JavaScript for this interactive activity. You can read all lesson explanations above without it.