javascript
exercises
exercises.js⚡javascript
/**
* ========================================
* 11.3 Fetch API and HTTP - Exercises
* ========================================
*
* Practice making HTTP requests with the Fetch API.
* Complete each exercise by filling in the code.
*/
/**
* EXERCISE 1: Basic GET Request
*
* Fetch data from a URL and return parsed JSON.
*/
async function fetchData(url) {
// Fetch from URL
// Return parsed JSON data
// Throw error with status code if not ok
}
// Test with:
// fetchData('https://jsonplaceholder.typicode.com/posts/1')
/**
* EXERCISE 2: POST Request
*
* Send JSON data to an endpoint.
*/
async function postData(url, data) {
// Send POST request with JSON body
// Include proper Content-Type header
// Return response JSON
}
// Test:
// postData('https://jsonplaceholder.typicode.com/posts', {
// title: 'Test',
// body: 'Content'
// })
/**
* EXERCISE 3: Query Parameters
*
* Fetch with URL query parameters.
*/
async function fetchWithQuery(baseUrl, params) {
// Build URL with query parameters
// Fetch and return JSON
}
// Test:
// fetchWithQuery('https://jsonplaceholder.typicode.com/posts', {
// userId: 1,
// _limit: 5
// })
/**
* EXERCISE 4: Error Handler
*
* Create a fetch wrapper with comprehensive error handling.
*/
async function safeFetch(url, options = {}) {
// Handle network errors
// Handle HTTP errors (4xx, 5xx)
// Return { success: true, data } or { success: false, error, status? }
}
// Test:
// safeFetch('https://httpstat.us/404')
// → { success: false, error: 'Not Found', status: 404 }
/**
* EXERCISE 5: Timeout
*
* Fetch with automatic timeout.
*/
async function fetchWithTimeout(url, timeoutMs = 5000) {
// Cancel request if it takes longer than timeout
// Throw descriptive error on timeout
}
// Test:
// fetchWithTimeout('https://httpstat.us/200?sleep=10000', 2000)
// → throws timeout error
/**
* EXERCISE 6: Retry Logic
*
* Retry failed requests with exponential backoff.
*/
async function fetchWithRetry(url, options = {}) {
// options: { retries: 3, delay: 1000, backoffFactor: 2 }
// Retry on network errors and 5xx status codes
// Exponential backoff between retries
}
// Test:
// fetchWithRetry('https://httpstat.us/503', { retries: 3 })
/**
* EXERCISE 7: Parallel Fetcher
*
* Fetch multiple URLs in parallel.
*/
async function fetchMany(urls) {
// Fetch all URLs in parallel
// Return array of { url, success, data?, error? }
}
// Test:
// fetchMany([
// 'https://jsonplaceholder.typicode.com/posts/1',
// 'https://jsonplaceholder.typicode.com/posts/999999',
// 'https://jsonplaceholder.typicode.com/posts/2'
// ])
/**
* EXERCISE 8: Sequential Fetcher
*
* Fetch URLs one after another.
*/
async function fetchSequence(urls) {
// Fetch URLs sequentially (wait for each before starting next)
// Return array of results
}
/**
* EXERCISE 9: Request with Auth
*
* Create authenticated request helper.
*/
function createAuthenticatedFetch(token) {
// Return a fetch function that adds Bearer token to all requests
// Should work like: authFetch(url, options)
}
// Test:
// const authFetch = createAuthenticatedFetch('my-secret-token');
// authFetch('/api/data')
/**
* EXERCISE 10: Progress Tracker
*
* Download with progress tracking.
*/
async function downloadWithProgress(url, onProgress) {
// Download file and report progress
// onProgress(percent, loaded, total)
// Return blob
}
/**
* EXERCISE 11: File Uploader
*
* Upload files using FormData.
*/
async function uploadFiles(url, files, metadata = {}) {
// files: FileList or array of Files
// metadata: additional form fields
// Return server response
}
/**
* EXERCISE 12: Cancellable Request
*
* Create request that can be cancelled.
*/
function createCancellableRequest(url, options = {}) {
// Return { promise, cancel }
// cancel() should abort the request
// Promise should reject with 'Request cancelled' on cancel
}
/**
* EXERCISE 13: Rate Limiter
*
* Limit requests to a certain rate.
*/
function createRateLimiter(requestsPerSecond = 5) {
// Return async function that rate-limits fetch calls
// Queue requests if limit exceeded
}
// Test:
// const limitedFetch = createRateLimiter(2);
// for (let i = 0; i < 10; i++) {
// limitedFetch(`/api/item/${i}`);
// }
/**
* EXERCISE 14: Response Cache
*
* Cache responses with TTL.
*/
class FetchCache {
constructor(ttlMs = 60000) {
// Initialize cache with time-to-live
}
async fetch(url, options = {}) {
// Return cached response if valid
// Otherwise fetch, cache, and return
}
invalidate(url) {
// Remove URL from cache
}
clear() {
// Clear entire cache
}
}
/**
* EXERCISE 15: API Client
*
* Full-featured API client.
*/
class APIClient {
constructor(baseUrl, defaultHeaders = {}) {
// Initialize with base URL and default headers
}
setHeader(name, value) {
// Set or update a default header
}
async get(endpoint, params = {}) {
// GET request with query params
}
async post(endpoint, data) {
// POST with JSON body
}
async put(endpoint, data) {
// PUT with JSON body
}
async patch(endpoint, data) {
// PATCH with JSON body
}
async delete(endpoint) {
// DELETE request
}
}
// ============================================
// SOLUTIONS (Hidden - Scroll to reveal)
// ============================================
/*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* SOLUTIONS BELOW
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
// SOLUTION 1: Basic GET Request
async function fetchDataSolution(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json();
}
// SOLUTION 2: POST Request
async function postDataSolution(url, data) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json();
}
// SOLUTION 3: Query Parameters
async function fetchWithQuerySolution(baseUrl, params) {
const url = new URL(baseUrl);
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, value);
}
});
const response = await fetch(url);
return response.json();
}
// SOLUTION 4: Error Handler
async function safeFetchSolution(url, options = {}) {
try {
const response = await fetch(url, options);
if (!response.ok) {
return {
success: false,
error: response.statusText || 'Request failed',
status: response.status,
};
}
const data = await response.json();
return { success: true, data };
} catch (error) {
return {
success: false,
error: error.message || 'Network error',
};
}
}
// SOLUTION 5: Timeout
async function fetchWithTimeoutSolution(url, timeoutMs = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error(`Request timed out after ${timeoutMs}ms`);
}
throw error;
}
}
// SOLUTION 6: Retry Logic
async function fetchWithRetrySolution(url, options = {}) {
const { retries = 3, delay = 1000, backoffFactor = 2 } = options;
let lastError;
for (let attempt = 0; attempt < retries; attempt++) {
try {
const response = await fetch(url);
// Retry on 5xx errors
if (response.status >= 500) {
throw new Error(`Server error: ${response.status}`);
}
return response;
} catch (error) {
lastError = error;
if (attempt < retries - 1) {
const waitTime = delay * Math.pow(backoffFactor, attempt);
await new Promise((r) => setTimeout(r, waitTime));
}
}
}
throw new Error(`Failed after ${retries} attempts: ${lastError.message}`);
}
// SOLUTION 7: Parallel Fetcher
async function fetchManySolution(urls) {
const results = await Promise.allSettled(
urls.map(async (url) => {
const response = await fetch(url);
if (!response.ok) throw new Error(response.statusText);
return response.json();
})
);
return urls.map((url, index) => {
const result = results[index];
if (result.status === 'fulfilled') {
return { url, success: true, data: result.value };
} else {
return { url, success: false, error: result.reason.message };
}
});
}
// SOLUTION 8: Sequential Fetcher
async function fetchSequenceSolution(urls) {
const results = [];
for (const url of urls) {
try {
const response = await fetch(url);
const data = await response.json();
results.push({ url, success: true, data });
} catch (error) {
results.push({ url, success: false, error: error.message });
}
}
return results;
}
// SOLUTION 9: Request with Auth
function createAuthenticatedFetchSolution(token) {
return async function (url, options = {}) {
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
};
}
// SOLUTION 10: Progress Tracker
async function downloadWithProgressSolution(url, onProgress) {
const response = await fetch(url);
const contentLength = response.headers.get('content-length');
const total = parseInt(contentLength, 10) || 0;
const reader = response.body.getReader();
const chunks = [];
let loaded = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
loaded += value.length;
if (total && onProgress) {
const percent = (loaded / total) * 100;
onProgress(percent, loaded, total);
}
}
return new Blob(chunks);
}
// SOLUTION 11: File Uploader
async function uploadFilesSolution(url, files, metadata = {}) {
const formData = new FormData();
// Add files
const fileArray = Array.from(files);
fileArray.forEach((file, index) => {
formData.append(`file${index}`, file);
});
// Add metadata
Object.entries(metadata).forEach(([key, value]) => {
formData.append(key, value);
});
const response = await fetch(url, {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
return response.json();
}
// SOLUTION 12: Cancellable Request
function createCancellableRequestSolution(url, options = {}) {
const controller = new AbortController();
const promise = fetch(url, {
...options,
signal: controller.signal,
}).catch((error) => {
if (error.name === 'AbortError') {
throw new Error('Request cancelled');
}
throw error;
});
return {
promise,
cancel: () => controller.abort(),
};
}
// SOLUTION 13: Rate Limiter
function createRateLimiterSolution(requestsPerSecond = 5) {
const queue = [];
const interval = 1000 / requestsPerSecond;
let lastRequest = 0;
let processing = false;
async function processQueue() {
if (processing || queue.length === 0) return;
processing = true;
while (queue.length > 0) {
const now = Date.now();
const timeSince = now - lastRequest;
if (timeSince < interval) {
await new Promise((r) => setTimeout(r, interval - timeSince));
}
const { url, options, resolve, reject } = queue.shift();
lastRequest = Date.now();
try {
const response = await fetch(url, options);
resolve(response);
} catch (error) {
reject(error);
}
}
processing = false;
}
return function limitedFetch(url, options = {}) {
return new Promise((resolve, reject) => {
queue.push({ url, options, resolve, reject });
processQueue();
});
};
}
// SOLUTION 14: Response Cache
class FetchCacheSolution {
constructor(ttlMs = 60000) {
this.cache = new Map();
this.ttl = ttlMs;
}
getCacheKey(url, options = {}) {
return `${options.method || 'GET'}-${url}`;
}
isValid(entry) {
return Date.now() < entry.expires;
}
async fetch(url, options = {}) {
const key = this.getCacheKey(url, options);
const cached = this.cache.get(key);
if (cached && this.isValid(cached)) {
return cached.data;
}
const response = await fetch(url, options);
const data = await response.json();
this.cache.set(key, {
data,
expires: Date.now() + this.ttl,
});
return data;
}
invalidate(url, options = {}) {
const key = this.getCacheKey(url, options);
this.cache.delete(key);
}
clear() {
this.cache.clear();
}
}
// SOLUTION 15: API Client
class APIClientSolution {
constructor(baseUrl, defaultHeaders = {}) {
this.baseUrl = baseUrl;
this.headers = {
'Content-Type': 'application/json',
...defaultHeaders,
};
}
setHeader(name, value) {
this.headers[name] = value;
}
async request(endpoint, options = {}) {
const url = new URL(endpoint, this.baseUrl);
// Add query params if present
if (options.params) {
Object.entries(options.params).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
}
const response = await fetch(url, {
...options,
headers: {
...this.headers,
...options.headers,
},
});
if (!response.ok) {
const error = new Error(`HTTP ${response.status}`);
error.status = response.status;
throw error;
}
if (response.status === 204) return null;
return response.json();
}
get(endpoint, params = {}) {
return this.request(endpoint, { method: 'GET', params });
}
post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data),
});
}
put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data),
});
}
patch(endpoint, data) {
return this.request(endpoint, {
method: 'PATCH',
body: JSON.stringify(data),
});
}
delete(endpoint) {
return this.request(endpoint, { method: 'DELETE' });
}
}
console.log('Fetch API exercises loaded!');
console.log(
'Complete each exercise and check against solutions at the bottom.'
);