The Fetch API is the modern standard for making network requests in JavaScript. This article covers Fetch API usage and best practices.
Basic Usage
Simple Requests
// Basic GET request
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// Full syntax
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
Request Methods
// GET request (default)
const getResponse = await fetch('/api/users');
// POST request
const postResponse = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'John', email: 'john@example.com' })
});
// PUT request
const putResponse = await fetch('/api/users/1', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Jane' })
});
// DELETE request
const deleteResponse = await fetch('/api/users/1', {
method: 'DELETE'
});
// PATCH request
const patchResponse = await fetch('/api/users/1', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ status: 'active' })
});
Request Configuration
Full Configuration Options
const response = await fetch(url, {
// Request method
method: 'POST',
// Request headers
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123',
'X-Custom-Header': 'value'
},
// Request body
body: JSON.stringify(data),
// Request mode
mode: 'cors', // 'cors', 'no-cors', 'same-origin'
// Credentials mode
credentials: 'include', // 'omit', 'same-origin', 'include'
// Cache mode
cache: 'no-cache', // 'default', 'no-store', 'reload', 'no-cache', 'force-cache'
// Redirect handling
redirect: 'follow', // 'follow', 'error', 'manual'
// Referrer policy
referrerPolicy: 'no-referrer-when-downgrade',
// Integrity check
integrity: 'sha256-abc123...',
// Keep alive
keepalive: false,
// Signal (for cancellation)
signal: abortController.signal
});
Headers Operations
// Using Headers object
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer token');
// Or pass object directly
const headers2 = new Headers({
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
});
// Methods
headers.set('X-Custom', 'value'); // Set (overwrite)
headers.append('Accept', 'application/json'); // Append
headers.delete('X-Custom'); // Delete
headers.get('Content-Type'); // Get
headers.has('Authorization'); // Check
// Iterate
for (const [key, value] of headers) {
console.log(`${key}: ${value}`);
}
// Use in request
await fetch(url, { headers });
Request Body Formats
// JSON data
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' })
});
// Form data
const formData = new FormData();
formData.append('name', 'John');
formData.append('file', fileInput.files[0]);
await fetch(url, {
method: 'POST',
body: formData // No need to set Content-Type, browser handles it
});
// URL encoded data
const params = new URLSearchParams();
params.append('name', 'John');
params.append('age', '25');
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
});
// Binary data
const buffer = new ArrayBuffer(8);
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/octet-stream' },
body: buffer
});
// Blob data
const blob = new Blob(['Hello'], { type: 'text/plain' });
await fetch(url, {
method: 'POST',
body: blob
});
Response Handling
Response Object
const response = await fetch(url);
// Response status
console.log(response.status); // Status code: 200, 404, 500...
console.log(response.statusText); // Status text: OK, Not Found...
console.log(response.ok); // Success (200-299)
// Response headers
console.log(response.headers.get('Content-Type'));
console.log(response.headers.get('Content-Length'));
// Response URL (may change after redirect)
console.log(response.url);
// Response type
console.log(response.type); // 'basic', 'cors', 'error', 'opaque'
// Redirected
console.log(response.redirected);
Parsing Response Body
const response = await fetch(url);
// JSON
const json = await response.json();
// Text
const text = await response.text();
// Blob (binary)
const blob = await response.blob();
// ArrayBuffer
const buffer = await response.arrayBuffer();
// FormData
const formData = await response.formData();
// Note: Body can only be read once
// Clone for multiple reads
const clone = response.clone();
const json1 = await response.json();
const json2 = await clone.json(); // Use clone
Streaming Reads
// Read large files or streaming data
async function readStream(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const contentLength = +response.headers.get('Content-Length');
let receivedLength = 0;
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
receivedLength += value.length;
// Progress
const progress = (receivedLength / contentLength * 100).toFixed(2);
console.log(`Download progress: ${progress}%`);
}
// Merge data
const allChunks = new Uint8Array(receivedLength);
let position = 0;
for (const chunk of chunks) {
allChunks.set(chunk, position);
position += chunk.length;
}
return allChunks;
}
// Read text stream
async function readTextStream(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let result = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
result += decoder.decode(value, { stream: true });
}
return result;
}
Error Handling
Complete Error Handling
async function fetchWithErrorHandling(url, options = {}) {
try {
const response = await fetch(url, options);
// Check HTTP status
if (!response.ok) {
// Try to parse error response
let errorMessage = response.statusText;
try {
const errorData = await response.json();
errorMessage = errorData.message || errorMessage;
} catch {
// Cannot parse JSON
}
throw new HttpError(response.status, errorMessage);
}
return response;
} catch (error) {
if (error instanceof HttpError) {
throw error;
}
// Network error
if (error.name === 'TypeError') {
throw new NetworkError('Network connection failed');
}
// Request aborted
if (error.name === 'AbortError') {
throw new AbortError('Request was cancelled');
}
throw error;
}
}
// Custom error classes
class HttpError extends Error {
constructor(status, message) {
super(message);
this.name = 'HttpError';
this.status = status;
}
}
class NetworkError extends Error {
constructor(message) {
super(message);
this.name = 'NetworkError';
}
}
class AbortError extends Error {
constructor(message) {
super(message);
this.name = 'AbortError';
}
}
// Usage
try {
const response = await fetchWithErrorHandling('/api/data');
const data = await response.json();
} catch (error) {
if (error instanceof HttpError) {
if (error.status === 401) {
redirectToLogin();
} else if (error.status === 404) {
showNotFound();
} else {
showError(`Server error: ${error.message}`);
}
} else if (error instanceof NetworkError) {
showError('Network connection failed, please check your connection');
} else if (error instanceof AbortError) {
console.log('Request was cancelled');
}
}
Advanced Features
Request Cancellation
// Using AbortController
const controller = new AbortController();
const signal = controller.signal;
// Make request
const fetchPromise = fetch(url, { signal });
// Cancel request
controller.abort();
// Timeout cancellation
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}
// Using AbortSignal.timeout() (newer API)
const response = await fetch(url, {
signal: AbortSignal.timeout(5000)
});
Request Retry
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (response.ok) {
return response;
}
// Only retry on server errors
if (response.status >= 500) {
throw new Error(`Server error: ${response.status}`);
}
// Don't retry client errors
return response;
} catch (error) {
lastError = error;
// Don't wait on last attempt
if (i < maxRetries - 1) {
// Exponential backoff
const delay = Math.pow(2, i) * 1000;
await new Promise(r => setTimeout(r, delay));
}
}
}
throw lastError;
}
// Advanced retry strategy
async function fetchWithAdvancedRetry(url, options = {}) {
const config = {
maxRetries: 3,
baseDelay: 1000,
maxDelay: 10000,
retryOn: [408, 429, 500, 502, 503, 504],
...options.retry
};
let attempt = 0;
while (true) {
try {
const response = await fetch(url, options);
if (response.ok || !config.retryOn.includes(response.status)) {
return response;
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
attempt++;
if (attempt >= config.maxRetries) {
throw error;
}
const delay = Math.min(
config.baseDelay * Math.pow(2, attempt - 1),
config.maxDelay
);
await new Promise(r => setTimeout(r, delay));
}
}
}
Concurrency Control
// Concurrency limiter
class ConcurrencyLimiter {
constructor(limit = 5) {
this.limit = limit;
this.running = 0;
this.queue = [];
}
async fetch(url, options) {
return new Promise((resolve, reject) => {
this.queue.push({ url, options, resolve, reject });
this.processQueue();
});
}
async processQueue() {
if (this.running >= this.limit || this.queue.length === 0) {
return;
}
this.running++;
const { url, options, resolve, reject } = this.queue.shift();
try {
const response = await fetch(url, options);
resolve(response);
} catch (error) {
reject(error);
} finally {
this.running--;
this.processQueue();
}
}
}
// Usage
const limiter = new ConcurrencyLimiter(3);
const urls = ['/api/1', '/api/2', '/api/3', '/api/4', '/api/5'];
const responses = await Promise.all(
urls.map(url => limiter.fetch(url))
);
HTTP Client Wrapper
Complete Wrapper
class HttpClient {
constructor(baseURL = '', defaultOptions = {}) {
this.baseURL = baseURL;
this.defaultOptions = {
headers: {
'Content-Type': 'application/json'
},
...defaultOptions
};
this.interceptors = {
request: [],
response: []
};
}
// Add request interceptor
addRequestInterceptor(fn) {
this.interceptors.request.push(fn);
}
// Add response interceptor
addResponseInterceptor(fn) {
this.interceptors.response.push(fn);
}
async request(endpoint, options = {}) {
let url = this.baseURL + endpoint;
let config = this.mergeOptions(options);
// Execute request interceptors
for (const interceptor of this.interceptors.request) {
const result = await interceptor({ url, ...config });
url = result.url || url;
config = { ...config, ...result };
}
try {
let response = await fetch(url, config);
// Execute response interceptors
for (const interceptor of this.interceptors.response) {
response = await interceptor(response);
}
if (!response.ok) {
throw new HttpError(response.status, response.statusText);
}
return response;
} catch (error) {
throw error;
}
}
mergeOptions(options) {
return {
...this.defaultOptions,
...options,
headers: {
...this.defaultOptions.headers,
...options.headers
}
};
}
async get(endpoint, params = {}) {
const queryString = new URLSearchParams(params).toString();
const url = queryString ? `${endpoint}?${queryString}` : endpoint;
const response = await this.request(url);
return response.json();
}
async post(endpoint, data) {
const response = await this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
return response.json();
}
async put(endpoint, data) {
const response = await this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
});
return response.json();
}
async delete(endpoint) {
const response = await this.request(endpoint, {
method: 'DELETE'
});
return response.json();
}
}
// Usage
const api = new HttpClient('https://api.example.com');
// Add auth interceptor
api.addRequestInterceptor(async (config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
return config;
});
// Add response interceptor
api.addResponseInterceptor(async (response) => {
if (response.status === 401) {
// Handle auth failure
redirectToLogin();
}
return response;
});
// API calls
const users = await api.get('/users', { page: 1, limit: 10 });
const newUser = await api.post('/users', { name: 'John' });
Best Practices Summary
Fetch API Best Practices:
┌─────────────────────────────────────────────────────┐
│ │
│ Error Handling │
│ ├── Check response.ok status │
│ ├── Handle network errors │
│ ├── Implement request timeouts │
│ └── Use appropriate retry strategies │
│ │
│ Performance │
│ ├── Use concurrency control │
│ ├── Use caching appropriately │
│ ├── Cancel requests to avoid waste │
│ └── Stream processing for large data │
│ │
│ Security │
│ ├── Configure CORS properly │
│ ├── Use HTTPS │
│ ├── Validate response data │
│ └── Protect sensitive information │
│ │
└─────────────────────────────────────────────────────┘
| Scenario | Recommended Config |
|---|---|
| Cross-origin | mode: ‘cors’, credentials: ‘include’ |
| Form upload | Don’t set Content-Type, use FormData |
| Large files | Streaming + progress display |
| Sensitive requests | credentials: ‘same-origin’ |
Master the Fetch API to build modern network request solutions.