const TIMEOUT_ERROR = new Error('timeout') const hexRe = /^[0-9A-Fa-f]+$/gu /** * Execute fetch and verify that the response was successful. * * @param request - Request information. * @param options - Fetch options. * @returns The fetch response. */ export async function successfulFetch(request: string, options?: RequestInit) { const response = await fetch(request, options) if (!response.ok) { throw new Error(`Fetch failed with status '${response.status}' for request '${request}'`) } return response } /** * Execute fetch and return object response. * * @param request - The request information. * @param options - The fetch options. * @returns The fetch response JSON data. */ export async function handleFetch(request: string, options?: RequestInit) { const response = await successfulFetch(request, options) const object = await response.json() return object } /** * Execute fetch and return object response, log if known error thrown, otherwise rethrow error. * * @param request - the request options object * @param request.url - The request url to query. * @param request.options - The fetch options. * @param request.timeout - Timeout to fail request * @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context * @returns The fetch response JSON data or undefined (if error occurs). */ export async function fetchWithErrorHandling({ url, options, timeout, errorCodesToCatch, }: { url: string options?: RequestInit timeout?: number errorCodesToCatch?: number[] }) { let result try { if (timeout) { result = Promise.race([ await handleFetch(url, options), new Promise((_, reject) => setTimeout(() => { reject(TIMEOUT_ERROR) }, timeout), ), ]) } else { result = await handleFetch(url, options) } } catch (e) { logOrRethrowError(e, errorCodesToCatch) } return result } /** * Fetch that fails after timeout. * * @param url - Url to fetch. * @param options - Options to send with the request. * @param timeout - Timeout to fail request. * @returns Promise resolving the request. */ export async function timeoutFetch(url: string, options?: RequestInit, timeout = 500): Promise { return Promise.race([ successfulFetch(url, options), new Promise((_, reject) => setTimeout(() => { reject(TIMEOUT_ERROR) }, timeout), ), ]) } /** * Utility method to log if error is a common fetch error and otherwise rethrow it. * * @param error - Caught error that we should either rethrow or log to console * @param codesToCatch - array of error codes for errors we want to catch and log in a particular context */ function logOrRethrowError(error: any, codesToCatch: number[] = []) { if (!error) { return } const includesErrorCodeToCatch = codesToCatch.some(code => error.message.includes(`Fetch failed with status '${code}'`), ) if ( error instanceof Error && (includesErrorCodeToCatch || error.message.includes('Failed to fetch') || error === TIMEOUT_ERROR) ) { console.error(error) } else { throw error } }