fetch()
fetch(input[, init]) The fetch() function is the modern, Promise-based way to make HTTP requests in JavaScript. It replaces the older XMLHttpRequest API with a cleaner, more powerful interface built on top of Promises.
Unlike XMLHttpRequest, fetch() automatically rejects the Promise on network failure, making error handling more intuitive. However, it does not reject on HTTP error statuses (4xx, 5xx)—you must check response.ok manually.
Syntax
fetch(url)
fetch(url, options)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
url | string or Request | — | The resource to fetch (URL string or Request object) |
init | RequestInit | undefined | Optional configuration object containing method, headers, body, etc. |
RequestInit Options
| Option | Type | Description |
|---|---|---|
method | string | HTTP method (GET, POST, PUT, DELETE, etc.). Default: GET |
headers | Headers or object | Request headers |
body | string or FormData or Blob or URLSearchParams | Request body (cannot be used with GET or HEAD) |
mode | string | CORS mode: “cors”, “no-cors”, or “same-origin” |
credentials | string | Include cookies: “omit”, “same-origin”, or “include” |
cache | string | Cache mode: “default”, “no-store”, “reload”, “no-cache”, “force-cache”, “only-if-cached” |
redirect | string | Follow, error, or manual redirect behavior |
signal | AbortSignal | AbortController signal to cancel the request |
referrer | string | Set the request’s Referer header |
referrerPolicy | string | Referrer policy |
Examples
Basic GET request
A basic fetch() call with await reads JSON from a public API endpoint. The response.json() method parses the body into a JavaScript object. Because fetch() returns a Promise, you can use async/await for a linear-looking control flow.
async function fetchUser() {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
const data = await response.json();
console.log(data);
// { id: 1, name: "Leanne Graham", email: "Sincere@april.biz", ... }
}
fetchUser();
GET requests don’t require an options object — fetch(url) defaults to GET. When you need to send data to the server, switch to a POST request with a body and the appropriate Content-Type header.
POST request with JSON body
For POST requests, provide a method, headers, and body in the options object. The Content-Type: application/json header tells the server how to interpret the payload, and JSON.stringify() converts the JavaScript object to a string before sending.
async function createPost() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: 'My New Post',
body: 'This is the post content.',
userId: 1
})
});
const data = await response.json();
console.log('Created:', data);
// Created: { title: "My New Post", body: "This is the post content.", userId: 1, id: 101 }
}
createPost();
A successful POST doesn’t guarantee the server accepted your data — the response could still carry a 4xx or 5xx status. Since fetch() never rejects on HTTP errors, you must inspect response.ok or response.status yourself.
Handling errors and checking response.ok
A try/catch block wraps the fetch call so network failures trigger the catch clause, and response.ok guards against HTTP-level errors. This two-tier approach lets you distinguish between a failed network request and a server that returned a 404 or 500.
async function safeFetch(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Fetch failed:', error.message);
throw error;
}
}
safeFetch('https://jsonplaceholder.typicode.com/posts/1');
// { userId: 1, id: 1, title: "...", body: "..." }
safeFetch('https://jsonplaceholder.typicode.com/nonexistent');
// Fetch failed: HTTP error! status: 404
Sometimes a request hangs indefinitely — a slow server or a network stall can leave your app waiting. AbortController gives you a way to cancel an in-flight fetch() after a timeout, preventing hung requests from piling up.
Canceling a request with AbortController
Create an AbortController, pass its signal into the fetch options, and call controller.abort() to cancel. The fetch Promise rejects with an AbortError, which you can distinguish from other errors by checking err.name. Always clear the timeout in a finally block to avoid leaking timers.
async function fetchWithTimeout(url, timeoutMs = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
return await response.json();
} finally {
clearTimeout(timeoutId);
}
}
fetchWithTimeout('https://jsonplaceholder.typicode.com/users/1', 1000)
.then(data => console.log(data))
.catch(err => console.error(err.name === 'AbortError' ? 'Request timed out' : err));
By default, fetch() does not include cookies in cross-origin requests. Setting credentials: 'include' changes that, which is necessary when your API relies on session cookies for authentication. The server must also respond with Access-Control-Allow-Credentials: true for the browser to accept the response.
Sending cookies with credentials
async function fetchWithCookies(url) {
const response = await fetch(url, {
credentials: 'include',
headers: {
'Accept': 'application/json'
}
});
return response.json();
}
For same-origin requests, credentials are included by default. The same-origin value is the default for credentials, while omit never sends them even on the same origin. Choose include only when you need cross-origin cookie exchange.
Common Patterns
Real-world fetch() usage often combines several of the techniques above into reusable helpers. The patterns below show how to wrap fetch() for JSON APIs, retry logic, and parallel requests.
JSON helper function:
async function fetchJSON(url, options = {}) {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
const user = await fetchJSON('https://api.example.com/user/1');
Wrapping fetch() in a helper centralizes error handling and default headers. A single JSON wrapper keeps your application code clean when every endpoint returns JSON. When network conditions are unreliable, you may want the request to retry rather than fail immediately.
Retry with exponential backoff:
async function fetchWithRetry(url, options = {}, retries = 3, delay = 1000) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok && i < retries - 1) {
await new Promise(r => setTimeout(r, delay * Math.pow(2, i)));
continue;
}
return response;
} catch (err) {
if (i === retries - 1) throw err;
await new Promise(r => setTimeout(r, delay * Math.pow(2, i)));
}
}
}
Exponential backoff increases the delay after each failed attempt — the delay * Math.pow(2, i) formula doubles the wait each time. This pattern avoids hammering a struggling server while still recovering automatically from transient failures. When you need data from several independent endpoints, retrying each sequentially would be slow; running them in parallel with Promise.all() is usually the better choice.
Parallel requests with Promise.all:
async function fetchMultiple(urls) {
const requests = urls.map(url => fetch(url).then(r => r.json()));
return Promise.all(requests);
}
const [users, posts] = await fetchMultiple([
'https://jsonplaceholder.typicode.com/users',
'https://jsonplaceholder.typicode.com/posts'
]);
Response body methods
A Response object exposes several methods for reading the response body, each returning a Promise that resolves to a different type:
response.json()— parses the body as JSON and returns a JavaScript valueresponse.text()— returns the body as a plain stringresponse.blob()— returns aBlob, useful for binary files and imagesresponse.arrayBuffer()— returns raw binary data as anArrayBufferresponse.formData()— returns aFormDataobject
Each body method can only be called once per response — calling response.json() twice on the same response throws because the stream has already been consumed. If you need to read the body multiple times, call response.clone() first.
fetch() vs XMLHttpRequest
fetch() replaced XMLHttpRequest as the standard way to make HTTP requests. The two main differences in behavior are: fetch() uses Promises instead of callbacks, and it does not automatically reject on HTTP error status codes (4xx, 5xx) — you must check response.ok or response.status yourself. XMLHttpRequest fired onerror only for network failures, while fetch() rejects its Promise on network failures but resolves it for HTTP errors. This design means you always need to inspect the response, which makes error handling explicit rather than implicit.
CORS and the mode option
By default, fetch() operates in "cors" mode, which means cross-origin requests must include appropriate Access-Control-Allow-Origin headers from the server. Setting mode: "no-cors" allows the request but returns an opaque response — the body is unreadable and the status is always 0. This is generally only useful for requests where you don’t need to read the response, such as sending analytics pings. For same-origin requests, "same-origin" mode explicitly prevents the request from going cross-origin.
See Also
AbortController— Cancel fetch requestsPromise.all()— Wait for multiple promises to resolve