Introduction to fetch
Think of fetch as your browser's delivery service. Just like how you might use a delivery app to order food, fetch lets you request data from servers. Here's the basic syntax:
// Basic fetch syntax
fetch(url, options)
// Simple GET request
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
// Async/await version
async function getData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
Making Different Types of Requests
1. GET Request (Retrieving Data)
// Fetching user data
async function getUser(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error('Failed to fetch user:', error);
throw error;
}
}
// With query parameters
async function searchProducts(query, category, page = 1) {
const params = new URLSearchParams({
q: query,
category: category,
page: page
});
try {
const response = await fetch(`/api/products/search?${params}`);
return await response.json();
} catch (error) {
console.error('Search failed:', error);
return [];
}
}
2. POST Request (Creating Data)
// Creating a new user
async function createUser(userData) {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to create user:', error);
throw error;
}
}
// Submitting a form
async function submitForm(formData) {
try {
const response = await fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams(formData)
});
return await response.json();
} catch (error) {
console.error('Form submission failed:', error);
throw error;
}
}
Working with Response Objects
The fetch API provides various methods to handle different types of responses:
async function handleMultipleResponseTypes() {
try {
const response = await fetch('/api/data');
// Check response type
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
return await response.json();
} else if (contentType?.includes('text/plain')) {
return await response.text();
} else if (contentType?.includes('application/pdf')) {
return await response.blob();
} else {
throw new Error(`Unsupported content type: ${contentType}`);
}
} catch (error) {
console.error('Request failed:', error);
throw error;
}
}
// Handling response headers
async function checkRateLimits() {
const response = await fetch('/api/data');
console.log({
remainingCalls: response.headers.get('X-RateLimit-Remaining'),
resetTime: response.headers.get('X-RateLimit-Reset')
});
}
Advanced Fetch Patterns
1. Request Timeout
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
}
// Usage
try {
const data = await fetchWithTimeout('/api/data', 3000);
console.log('Data received:', data);
} catch (error) {
console.error('Request failed:', error);
}
2. Retry Logic
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
const isLastAttempt = i === maxRetries - 1;
if (isLastAttempt) throw error;
// Wait longer between each retry
const delay = Math.min(1000 * Math.pow(2, i), 10000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
3. Request Queue
class RequestQueue {
constructor(concurrency = 3) {
this.concurrency = concurrency;
this.queue = [];
this.active = 0;
}
async add(request) {
if (this.active >= this.concurrency) {
// Wait for a slot to open
await new Promise(resolve => this.queue.push(resolve));
}
this.active++;
try {
return await request();
} finally {
this.active--;
if (this.queue.length > 0) {
// Let the next request proceed
this.queue.shift()();
}
}
}
}
// Usage
const queue = new RequestQueue(2);
async function processUrls(urls) {
const results = [];
for (const url of urls) {
const result = await queue.add(async () => {
const response = await fetch(url);
return response.json();
});
results.push(result);
}
return results;
}
Error Handling Best Practices
// Comprehensive error handler
async function safeFetch(url, options = {}) {
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
// Handle different types of errors
if (response.status === 404) {
throw new Error('Resource not found');
}
if (response.status === 401) {
// Handle authentication error
window.location.href = '/login';
throw new Error('Authentication required');
}
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Request failed');
}
return await response.json();
} catch (error) {
// Log error for debugging
console.error('Request failed:', {
url,
error: error.message,
timestamp: new Date().toISOString()
});
// Rethrow with user-friendly message
throw new Error('Unable to complete request. Please try again later.');
}
}
// Usage with different scenarios
async function handleUserData() {
try {
// GET request
const userData = await safeFetch('/api/user/profile');
// POST request
await safeFetch('/api/user/settings', {
method: 'POST',
body: JSON.stringify({ theme: 'dark' })
});
// Update UI
updateUserInterface(userData);
} catch (error) {
// Show user-friendly error message
showErrorNotification(error.message);
}
}