Fetch Promise and Response Handling
The Fetch API returns a promise that normally resolves to a `Response` once the HTTP exchange produces a response, including HTTP error statuses such as 404 or 500. Code must therefore check the response status and explicitly parse the response body before using it.
Calling `fetch()` returns a promise. When the request completes with an HTTP response, that promise resolves to a `Response` object containing metadata such as `status`, `statusText`, `headers`, and methods for reading the body.
A successful promise does not mean the HTTP request was successful. By default, HTTP statuses such as 404 and 500 do not cause `fetch()` to reject. Network failures, malformed requests at the transport level, and similar failures can reject the promise instead.
Use `response.ok` as a convenient boolean check for an HTTP status in the 200–299 range. For more detailed handling, inspect `response.status` or related response metadata.
The response body is consumed separately. For JSON, `await response.json()` reads and parses the body; for text, use `await response.text()`. Body-reading methods are asynchronous and should generally be called only once for a given response body unless the response has been cloned.
Key points
- `fetch()` resolves with a `Response` for HTTP error statuses such as 404.
- `response.ok` is `true` for HTTP statuses from 200 through 299.
- Check HTTP status before treating the response as application-level success.
- Use `response.json()` or `response.text()` to asynchronously consume the body.
- Network-level failures can reject the `fetch()` promise.
Interview questions
Does `fetch()` reject when the server returns a 404?
Normally, no. A 404 is an HTTP response, so `fetch()` generally resolves with a `Response` object. The application should inspect `response.ok` or `response.status` to detect the HTTP failure. The promise can reject for network-level failures instead.
What is the purpose of `response.ok`?
`response.ok` is a boolean indicating whether the HTTP status is in the 2xx range. It is useful when the application only needs to distinguish HTTP success from HTTP failure. If more precise behavior is required, inspect `response.status` instead.
Why do you need `await response.json()` after `await fetch()`?
`fetch()` resolves to a `Response`, not directly to the decoded application data. The response body is read asynchronously, so `response.json()` returns a promise that must be awaited or otherwise handled asynchronously. JSON parsing then produces the JavaScript value represented by the body.
How would you structure error handling around a JSON API request?
First, await `fetch()` inside a `try` block so network or other promise failures can be handled. Then check `response.ok` before treating the HTTP response as successful. For a successful response, await `response.json()` and handle parsing errors as appropriate. For an HTTP error, use the status and, when useful, an error payload to construct an application-level error.
What does it mean that a response body is consumable?
The body is exposed as a stream, and normal body-reading methods consume that stream. After the body has been consumed, another read such as a second `json()` call will not normally work. If two independent consumers need the body, `response.clone()` can create another response whose body can be consumed separately. This matters when middleware, logging, or application code all need access to the payload.