/** * Utility function for making HTTP requests with timeout * * This is a critical production utility to prevent hanging requests * that can exhaust server resources. */ export interface FetchWithTimeoutOptions extends RequestInit { timeout?: number; // Timeout in milliseconds (default: 30000) } /** * Fetch with automatic timeout handling * * @param url - The URL to fetch * @param options - Fetch options including optional timeout * @returns Promise * @throws Error if timeout is exceeded or request fails * * @example * ```typescript * const response = await fetchWithTimeout('https://api.example.com/data', { * method: 'GET', * timeout: 10000, // 10 seconds * headers: { 'Authorization': 'Bearer token' } * }); * ``` */ export async function fetchWithTimeout( url: string, options: FetchWithTimeoutOptions = {} ): Promise { const { timeout = 30000, ...fetchOptions } = options; // Create AbortController for timeout const controller = new AbortController(); const timeoutId = setTimeout(() => { controller.abort(); }, timeout); try { const response = await fetch(url, { ...fetchOptions, signal: controller.signal, }); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); if (error instanceof Error && error.name === 'AbortError') { throw new Error(`Request timeout after ${timeout}ms: ${url}`); } throw error; } } /** * Fetch with timeout and automatic JSON parsing * * @param url - The URL to fetch * @param options - Fetch options including optional timeout * @returns Promise - Parsed JSON response * * @example * ```typescript * const data = await fetchJsonWithTimeout('https://api.example.com/data', { * method: 'GET', * timeout: 10000, * }); * ``` */ export async function fetchJsonWithTimeout( url: string, options: FetchWithTimeoutOptions = {} ): Promise { const response = await fetchWithTimeout(url, options); if (!response.ok) { const errorText = await response.text().catch(() => 'Unknown error'); throw new Error( `HTTP ${response.status} ${response.statusText}: ${errorText.substring(0, 200)}` ); } const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { throw new Error(`Expected JSON response, got ${contentType}`); } return response.json(); }