JavaScript Fetch Table

PieceWhat it doesField note
fetch(url)Starts a request; resolves with a ResponseRejects ONLY on network failure - see the catch below
{ method: 'POST' }Second argument options objectGET/POST/PUT/PATCH/DELETE - default GET, no body allowed on GET
{ headers: {...} }Request headersContent-Type: application/json is set by hand - fetch will not guess
{ body: JSON.stringify(x) }Request payloadStrings, Blob, FormData, URLSearchParams - objects must be stringified
response.oktrue for status 200-299THE gotcha: 404 and 500 still resolve - check ok before reading data
response.statusHTTP status codePair with ok for error branches the server actually sent
response.json()Parses body as JSONReturns a promise; rejects with SyntaxError on invalid JSON
response.text() / .blob()Body as text or binaryA body can be read exactly once - clones need response.clone()
response.headers.get(n)Read a response headerCase-insensitive; CORS exposes only a safe list cross-origin
AbortController.signalCancels an in-flight requestcontroller.abort() rejects with a TimeoutError-shaped DOMException
{ credentials: 'include' }Send cookies cross-originDefault is same-origin only; server must answer Access-Control-Allow-Credentials
catch (e)Network failures and abortsNever fires for 404/500 - those are successful HTTP exchanges
Reference: the MDN fetch guide. The one rule worth tattooing: fetch rejects only when the request could not complete - a 404 is a perfectly successful conversation with the server, so it resolves. Error handling therefore has two layers: catch for the network, and an if (!response.ok) throw for the status. Bottom line: always set Content-Type on JSON POSTs, always check response.ok, and wire an AbortController for anything a user can navigate away from. Related tools: JS promise table for the mechanics underneath, json formatter to inspect what came back, and URL encoder for the query strings you build.

fetch is the browser's HTTP client: one function, an options object, and a Response whose body you read once. The table below is the working twelve - everything a real request needs from the URL to the abort signal - with the field notes that separate working code from code that only works on the happy path.

Bottom line: fetch rejects ONLY when the request could not complete - DNS failure, network down, aborted. A 404 or 500 is a successful HTTP exchange and resolves normally, which is why every production fetch has two error layers: catch for the network, and an if (!response.ok) throw for the status the server actually sent.

The honest part: the second argument does more work than the function itself. Content-Type is set by hand (fetch never guesses that your body is JSON), credentials are same-origin by default (cookies vanish cross-origin until you opt in), and the response body can be read exactly once - response.clone() exists because someone needed the bytes twice.

How to use

  1. Build the request top-down: URL, method, headers, body - each row is one piece of the options object.
  2. Read the response in order: check response.ok and status BEFORE parsing; then pick json(), text() or blob() by content.
  3. Add the safety net last: AbortController for user-navigable requests, and a catch that knows it will only ever see network errors.

Frequently asked questions

Why doesn't fetch throw an error on 404 or 500?

Because the HTTP exchange succeeded - the server was reached, it answered, the response is complete. fetch's promise rejects only for failures BEFORE a response exists: DNS lookup failure, refused connection, network drop, or a manual abort. The status code is data, not an exception, so the standard pattern wraps it explicitly: if (!response.ok) throw new Error('HTTP ' + response.status). After that throw, your existing catch handles both layers uniformly.

How do I send a JSON POST request?

Three pieces, all required: method: 'POST', headers: { 'Content-Type': 'application/json' }, and body: JSON.stringify(data). The header is the step people skip - fetch will happily send the stringified object with no content type, and the server's parser cannot know it is JSON. On the way back, response.json() parses the body and, unlike fetch itself, rejects with a SyntaxError when the body is not valid JSON.

How do I cancel a fetch request?

Create an AbortController, pass controller.signal as the signal option, and call controller.abort() when the request should die - on route change, on a newer search superseding an older one, or on a timeout via setTimeout. The pending fetch rejects with a DOMException named AbortError, which your catch can distinguish by name and ignore. A second use: Signal.timeout(ms) in newer browsers builds the same pattern in one line.

Where did my cookies go in a cross-origin fetch?

Into the default: credentials defaults to same-origin, so cookies travel only when the request targets the origin that set them. Cross-origin needs credentials: 'include' on the request AND Access-Control-Allow-Credentials: true on the server response AND a non-wildcard Access-Control-Allow-Origin - three switches, all required. Missing any one silently drops the cookie, which is why authenticated cross-origin fetches feel randomly broken.

Related tools