import('../lib/fetch.js') 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; } }