fetch() starts an HTTP request and resolves to a Response when headers arrive. It rejects for network or cancellation failures, but an HTTP 404 or 500 still resolves, so application code must check response.ok or response.status.
A reliable request also defines cancellation, decoding, credentials, error messages, and whether repeating the operation is safe.
The Fetch API is a Promise-based browser interface built around Request, Response, and Headers objects. A fetch promise resolves when response headers arrive even for HTTP errors, so check `response.ok` or `response.status` before decoding the body.
// ---- GET request ----
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => {
console.log('Status:', response.status); // 200
console.log('OK?', response.ok); // true
console.log('Type:', response.type); // "cors" or "basic"
return response.json(); // returns a Promise
})
.then(post => console.log(post.title))
.catch(err => console.error('Fetch failed:', err));
// ---- POST request ----
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: 'Hello World',
body: 'This is the post body',
userId: 1
})
})
.then(res => res.json())
.then(data => console.log('Created post with id:', data.id));
The Response object returned by fetch() provides several methods to read the body. Each method returns a Promise and can only be called once per response.
// response.json()
fetch('/api/users')
.then(res => res.json())
.then(users => console.log(users));
// response.text() - useful for HTML fragments or plain text
fetch('/api/message')
.then(res => res.text())
.then(text => document.getElementById('msg').textContent = text);
// response.blob() - download and display an image
fetch('/images/avatar.png')
.then(res => res.blob())
.then(blob => {
const url = URL.createObjectURL(blob);
document.getElementById('avatar').src = url;
});
// Checking response.ok before processing
fetch('/api/data')
.then(res => {
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
return res.json();
})
.then(data => console.log(data))
.catch(err => console.error(err.message));
The Headers class provides a convenient interface for working with HTTP headers in both requests and responses.
// Build headers with the Headers constructor
const myHeaders = new Headers();
myHeaders.append('Content-Type', 'application/json');
myHeaders.append('Authorization', 'Bearer my-token-here');
myHeaders.append('X-Custom-Header', 'my-value');
fetch('/api/secure', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify({ data: 'payload' })
});
// Read response headers
fetch('/api/info').then(res => {
console.log(res.headers.get('Content-Type'));
console.log(res.headers.get('X-Rate-Limit'));
// Iterate all response headers
res.headers.forEach((value, name) => {
console.log(`${name}: ${value}`);
});
});
// AbortController lets you cancel a fetch request
const controller = new AbortController();
const signal = controller.signal;
// Cancel after 5 seconds
const timeoutId = setTimeout(() => controller.abort(), 5000);
fetch('/api/slow-data', { signal })
.then(res => res.json())
.then(data => {
clearTimeout(timeoutId); // clear timeout if request succeeds
console.log('Data:', data);
})
.catch(err => {
if (err.name === 'AbortError') {
console.warn('Request was cancelled (timeout or manual abort)');
} else {
console.error('Fetch error:', err);
}
});
// Cancel manually (e.g., user clicks a cancel button)
document.getElementById('cancel-btn').addEventListener('click', () => {
controller.abort();
console.log('Request cancelled by user');
});
For same-origin requests, browser credentials are included by default. A cross-origin request needs credentials: "include" when cookies or HTTP authentication must travel, and the server must return compatible CORS headers. The client cannot fix a missing Access-Control-Allow-Origin response.
A custom header or non-simple content type may trigger a preflight OPTIONS request. Authentication cookies should still use Secure, HttpOnly, and an appropriate SameSite policy, and state-changing requests need CSRF protection.
Fetch has no universal elapsed-time timeout option; abort it with an AbortSignal. Distinguish user cancellation from a timeout so the interface can respond accurately, and always cancel obsolete work when a view changes.
Retry only transient failures and only when the operation is idempotent or the API supports an idempotency key. Honor Retry-After, use bounded exponential backoff with jitter, and never create an endless retry loop.
| Failure | Retry? | Reason |
|---|---|---|
| Network interruption | Sometimes | A bounded retry may recover. |
| 429 or temporary 5xx | Sometimes | Follow server guidance and a retry budget. |
| 400 validation error | No | The request must change. |
| Unkeyed payment POST | No | Repeating may duplicate the side effect. |
Try this next
0 of 2 completed
Explore 500+ free tutorials across 20+ languages and frameworks.