95 lines
2.4 KiB
TypeScript
95 lines
2.4 KiB
TypeScript
/**
|
|
* 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<Response>
|
|
* @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<Response> {
|
|
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<T> - Parsed JSON response
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const data = await fetchJsonWithTimeout('https://api.example.com/data', {
|
|
* method: 'GET',
|
|
* timeout: 10000,
|
|
* });
|
|
* ```
|
|
*/
|
|
export async function fetchJsonWithTimeout<T = any>(
|
|
url: string,
|
|
options: FetchWithTimeoutOptions = {}
|
|
): Promise<T> {
|
|
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();
|
|
}
|