If you have ever moved from XMLHttpRequest or a library to native browser features, you have probably run into the fetch API. It looks simple, but small details like error handling, timeouts, and uploads can trip people up.
Hi folks,
I keep seeing examples that use fetch, but I am not totally clear on best practices and edge cases. What is the fetch API in JavaScript? How do I use it for GET and POST requests, set timeouts, upload files, parse JSON safely, and handle errors correctly? Any pitfalls I should know about with CORS or credentials?
Thanks!
The fetch API is a modern, promise-based interface for making HTTP requests in the browser and in many runtimes. It returns a Response object and only rejects on network failures or aborts, not on HTTP error status by default. Let’s dive a little deeper into how you can use it.
async function getJSON(url, options = {}) {
const res = await fetch(url, options);
if (!res.ok) {
// fetch does not throw on 4xx or 5xx, so check .ok
throw new Error(`HTTP ${res.status} ${res.statusText}`);
}
// Guard against invalid JSON
const text = await res.text();
return text ? JSON.parse(text) : null;
}
getJSON('/api/user')
.then(data => console.log(data))
.catch(err => console.error('Request failed:', err));Code language: JavaScript (javascript)
async function postJSON(url, body) {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
postJSON('/api/todos', { title: 'Buy milk' });Code language: JavaScript (javascript)
async function fetchWithTimeout(url, options = {}, timeoutMs = 8000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(url, { ...options, signal: controller.signal });
return res;
} finally {
clearTimeout(id);
}
}
// Usage
fetchWithTimeout('/api/slow', {}, 5000)
.then(r => r.text())
.catch(err => {
if (err.name === 'AbortError') console.error('Request timed out');
});Code language: JavaScript (javascript)
async function uploadFile(url, file) {
const form = new FormData();
form.append('file', file);
// append any other fields your server expects
const res = await fetch(url, { method: 'POST', body: form });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}Code language: JavaScript (javascript)
For large downloads, you can read the response body as a stream and emit progress updates.
async function downloadWithProgress(url, onProgress) {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const contentLength = Number(res.headers.get('Content-Length')) || 0;
const reader = res.body.getReader();
let received = 0;
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
received += value.length;
if (contentLength) onProgress(received / contentLength);
}
return new Blob(chunks);
}Code language: JavaScript (javascript)
- By default, fetch does not send cookies. Use
credentials: 'include‘ to send them cross-site or'same-origin'for same-site requests. - CORS is enforced by the browser. Your server must return the proper Access-Control-Allow-Origin headers and, if sending credentials, must not use a wildcard origin.
- If you manage media on a CDN, clean and predictable image URLs help avoid surprises when constructing requests.
async function fetchRetry(url, options = {}, retries = 3, backoff = 300) {
try {
const res = await fetch(url, options);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res;
} catch (err) {
if (retries > 0) {
await new Promise(r => setTimeout(r, backoff));
return fetchRetry(url, options, retries - 1, backoff * 2);
}
throw err;
}
}Code language: JavaScript (javascript)
After you grasp the basics, you can apply fetch to media workflows. For example, uploading images from the browser to Cloudinary with an unsigned preset:
async function uploadToCloudinary(file) {
const url = 'https://api.cloudinary.com/v1_1/<cloud_name>/image/upload';
const form = new FormData();
form.append('file', file);
form.append('upload_preset', '<unsigned_preset>'); // configured in your account
const res = await fetch(url, { method: 'POST', body: form });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json(); // contains secure_url and more
return data.secure_url;
}
// After upload, deliver an optimized variant
// f_auto picks a modern format like WebP or AVIF, q_auto adjusts quality
// Example: https://res.cloudinary.com/<cloud_name>/image/upload/f_auto,q_auto/your_public_id.jpgCode language: JavaScript (javascript)
Converting to modern formats often improves performance. If you are hosting media, this primer on understanding image hosting for websites shows how delivery, caching, and CORS policies affect real-world loading times.
- Validate and sanitize any user-provided URLs before sending them to fetch.
- Prefer POST with JSON for complex inputs, and use CSRF protections for same-site forms.
- Use TLS for all endpoints and do not log sensitive headers or tokens.
- fetch is a promise-based HTTP API. Check
res.okto handle HTTP errors and use AbortController for timeouts. - Use FormData for uploads, JSON for structured requests, and streams for progress.
- Mind CORS and credentials. Predictable image URLs and correct headers keep things smooth.
- Upload with fetch and deliver optimized variants using Cloudinary for fast media experiences.
Ready to optimize your media workflows and deliver fast experiences? Sign up for Cloudinary and start building with powerful upload, transformation, and delivery tools.