Fetch API

On this page 8

The Fetch API is the standard promise-based interface for making HTTP requests from browser JavaScript and processing their responses. Its central function, fetch(), takes a resource address and optional settings and returns a Promise that resolves to a Response object, which can then be read as text, JSON, binary data or a stream.

Fetch replaced the older XMLHttpRequest as the modern way to load data without reloading the page. It is available in all current browsers, in web workers and service workers, and in server-side runtimes such as Node.js, which makes it the common request primitive across the JavaScript ecosystem.

How a fetch request works

Calling fetch() starts a network request and immediately returns a pending Promise. The Promise resolves as soon as the response headers arrive, not when the whole body has downloaded. Reading the body is a second asynchronous step, using a method such as response.json(), which returns another Promise.

With async/await, the usual pattern is two awaits: one for the response and one for the body. Between them, code should check response.ok, which is true only for status codes 200 to 299, and handle other statuses explicitly.

The API is built from a few interfaces that also appear in service workers and the Cache API. The table summarizes them.

InterfaceRepresentsKey members
fetch()The request functionResource, options object, returns a Promise
RequestAn outgoing requestmethod, headers, body, signal
ResponseA received responsestatus, ok, headers, body methods
HeadersA case-insensitive header mapget(), set(), append(), has()
AbortControllerA cancellation handlesignal, abort()
Core interfaces of the Fetch API

Reading the response body

A Response exposes its body through several reader methods, each returning a Promise. The body is a one-time stream: after one method has consumed it, response.bodyUsed becomes true and any further read fails, so the format must be chosen before reading.

  • json() parses the body as JSON and rejects on malformed input, including an empty body.
  • text() returns a string, useful for HTML fragments, CSV or diagnosing unexpected responses.
  • blob() and arrayBuffer() return binary data for images, files and audio.
  • formData() parses multipart or URL-encoded form bodies.
  • response.body gives direct access to a ReadableStream for progressive processing.

Streaming lets an application process data as it arrives, for example rendering lines of a large log or tokens of a generated answer, instead of waiting for the full download. Reading the stream chunk by chunk also makes it possible to show download progress when the server sends a Content-Length header.

Request options and their effects

The second argument of fetch() is an options object that configures the request. Several options affect security and caching, so their defaults are worth knowing precisely.

OptionDefaultPurpose
methodGETHTTP method such as POST, PUT, DELETE
headersNone addedContent type, authorization, custom headers
bodyNoneString, FormData, Blob, URLSearchParams or stream
modecorsCross-origin behavior: cors, same-origin, no-cors
credentialssame-originWhether cookies are sent: omit, same-origin, include
cachedefaultInteraction with the HTTP cache
signalNoneAbortSignal for cancellation or timeout
Important fetch options with defaults

When sending JSON, the body must be serialized with JSON.stringify() and the Content-Type header set to application/json. When sending FormData, the header should be left unset, because the browser must add the multipart boundary itself.

Fetch compared with XMLHttpRequest and libraries

Fetch is lower-level and more consistent than its predecessor, but it deliberately omits conveniences that some libraries provide. Knowing the differences prevents subtle bugs during migration.

AspectFetch APIXMLHttpRequestTypical HTTP library
Programming modelPromisesEvents and callbacksPromises
HTTP error statusesResolve normallyResolve normallyOften reject
Upload progressNot directly availableAvailableUsually available
Streaming bodyYes, ReadableStreamLimitedVaries
CancellationAbortControllerabort() methodUsually supported
Use in service workersYesNoDepends on internals
Fetch API compared with alternatives

The most important behavioral difference is that fetch rejects only on network failure, such as a lost connection, a DNS error or a CORS block. A 404 or 500 response resolves successfully, so code that skips the ok check treats server errors as data.

Cancellation, timeouts and CORS

Fetch has no built-in timeout option. Requests are canceled with an AbortController: its signal is passed in the options, and calling abort() rejects the Promise with an AbortError. Modern browsers also offer AbortSignal.timeout(), which creates a signal that aborts automatically after a set number of milliseconds.

Cross-origin requests follow the same-origin policy and CORS. The server must return appropriate Access-Control-Allow-Origin headers, and requests with custom headers or methods other than simple ones trigger a preflight OPTIONS request. The no-cors mode does not bypass this; it returns an opaque response whose body and status cannot be read.

Common mistakes with the Fetch API

Most fetch bugs come from assumptions carried over from other HTTP tools or from forgetting that the body is read separately.

SymptomCauseFix
Error page parsed as dataNo response.ok checkCheck status and throw on failure
Body already used errorBody read twiceRead once or use response.clone()
Cookies missing cross-originDefault credentials settingSet credentials to include with CORS support
Request hangs indefinitelyNo timeoutUse an AbortSignal with a timeout
Stale results in a search boxEarlier responses arrive lateAbort the previous request on new input
Empty opaque responseno-cors modeConfigure CORS on the server instead
Frequent Fetch API errors, causes and fixes

Explained without jargon

The Fetch API is how a web page asks a server for information while you stay on the page. It works like ordering at a counter and getting a receipt. The receipt promises that your order will come, and the page keeps working while it waits.

When the answer arrives, the page reads it and updates what you see. This is how a weather widget refreshes or a search box suggests results without reloading the whole page.

A typical use case

An online store adds live search suggestions. Each time the shopper types, the script calls fetch() with the current query, waits for the response and renders up to eight matching products below the search field.

Early testing reveals two problems. Fast typists see suggestions for an older query flash over newer ones, and a slow server occasionally leaves the spinner running forever. The developer creates a new AbortController for every keystroke and aborts the previous request, adds a 300 millisecond debounce, and combines the signal with a 5 second timeout. Responses with a status other than 200 to 299 now show a short retry message instead of an empty list.

After release, suggestions always match the latest input, server load from search drops noticeably because of the debounce, and no stuck loading states are reported.