/** * Fetches all items from a paginated API endpoint. * Assumes the API response structure includes: * - `results`: An array of items for the current page. * - `next`: A URL string for the next page, or null if it's the last page. * @param {string} initialUrl The first URL to fetch. * @param {RequestInit} [options] Fetch options. * @returns {Promise>} A promise that resolves with an array of all collected items. */ async function fetchAllPaginatedData(initialUrl, options = {}) { let allItems = []; let nextUrl = initialUrl; while (nextUrl) { try { console.log(`Fetching from: ${nextUrl}`); const response = await fetch(nextUrl, options); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status} - ${response.statusText}`); } const data = await response.json(); if (data && Array.isArray(data.results)) { allItems = allItems.concat(data.results); } else { console.warn('Paginated API response did not contain a "results" array or was unexpected.'); // Optionally break or handle this non-standard response } nextUrl = data.next || null; // Move to the next page URL } catch (error) { console.error(`Error fetching paginated data from ${nextUrl}:`, error); throw error; // Re-throw to indicate failure } } return allItems; } // Usage example: async function loadAllProducts() { const productsApiUrl = 'https://api.example.com/products?page=1'; // Example starting URL // Mock API response structure for demonstration // In a real scenario, this would be handled by the backend let mockPage = 1; const mockApiFetch = async (url) => { // Simulate API delay await new Promise(resolve => setTimeout(resolve, 500)); if (url.includes('page=1') && mockPage === 1) { mockPage++; return { ok: true, json: async () => ({ results: [{id: 1, name: 'Product A'}, {id: 2, name: 'Product B'}], next: 'https://api.example.com/products?page=2', }), status: 200, statusText: 'OK', }; } else if (url.includes('page=2') && mockPage === 2) { mockPage++; return { ok: true, json: async () => ({ results: [{id: 3, name: 'Product C'}, {id: 4, name: 'Product D'}], next: 'https://api.example.com/products?page=3', }), status: 200, statusText: 'OK', }; } else if (url.includes('page=3') && mockPage === 3) { mockPage++; return { ok: true, json: async () => ({ results: [{id: 5, name: 'Product E'}], next: null, // Last page }), status: 200, statusText: 'OK', }; } return { ok: false, status: 404, statusText: 'Not Found', json: async () => ({ message: 'Page not found' }) }; }; // Temporarily override global fetch for demo purposes const originalFetch = globalThis.fetch; globalThis.fetch = mockApiFetch; try { const allProducts = await fetchAllPaginatedData(productsApiUrl, { // headers: { 'Authorization': 'Bearer YOUR_TOKEN' } }); console.log('All products:', allProducts); // Expected output: // [{id: 1, name: 'Product A'}, {id: 2, name: 'Product B'}, {id: 3, name: 'Product C'}, {id: 4, name: 'Product D'}, {id: 5, name: 'Product E'}] } catch (error) { console.error('Failed to fetch all products:', error); } finally { globalThis.fetch = originalFetch; // Restore original fetch } } loadAllProducts();