From 1a945b7b04c6afc0c4266f1289aac429ff8f83aa Mon Sep 17 00:00:00 2001 From: dnlbui Date: Tue, 23 Jul 2024 12:22:20 -0500 Subject: [PATCH] Refactor fetcher.ts to improve error handling and readability --- hooks/fetcher.ts | 107 ++++++++++++----------------------------------- 1 file changed, 26 insertions(+), 81 deletions(-) diff --git a/hooks/fetcher.ts b/hooks/fetcher.ts index 31ca344..3a0f75e 100644 --- a/hooks/fetcher.ts +++ b/hooks/fetcher.ts @@ -4,107 +4,52 @@ import { isDev } from '../utils/is-dev' const { apiBase } = useGlobals() -function delay(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)) +function showErrorToast(showToast: (msg: string) => void, status: number, resource: string): void { + const message = `Error (${status}): Failed to fetch ${resource}. Please try again or contact support.` + showToast(`${message}`) } -class FetchError extends Error { - status: number - constructor(message: string, status: number) { - super(message) - this.status = status - } -} - -// Helper function to show error toast messages -function showErrorToast( - showToast: (msg: string) => void, - status: number, - input: RequestInfo | URL -) { - // Extract the last segment from the input URL - const urlString = - input instanceof Request - ? input.url - : typeof input === "string" - ? input - : input.href - const url = new URL(urlString) - const lastSegment = - url.pathname.split("/").filter(Boolean).pop() || "resource" - - // Construct the base message, including the status if it is not undefined or null - const baseMessage = `Error${ - status !== undefined && status !== null ? ` (${status})` : "" - }: An error occurred while retrieving ${lastSegment}.` - const reportMessage = `Please report this issue to our support team if the problem persists.` - - showToast(`${baseMessage} ${reportMessage}`) -} - -export const fetcher = async ( +export async function fetcher( input: RequestInfo | URL, init: RequestInit, showToast: (msg: string) => void, - retries: number = 2, // Default number of retries - retryDelay: number = 1000 // Default delay between retries in milliseconds -): Promise => { - let attempt = 0 - while (attempt < retries) { + retries: number = 2 +): Promise { + const resource = new URL(input.toString()).pathname.split('/').pop() || 'resource' + + for (let attempt = 0; attempt < retries; attempt++) { try { const res = await fetch(input, { - headers: { - "Content-Type": "application/json", - }, - credentials: "include", - ...(init ?? {}), + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + ...init, }) - const data = await res.json() - - // Handle specific status codes without retries if (res.status === 403) { await authService.logout(apiBase) - throw new FetchError("Unauthorized", res.status) + showErrorToast(showToast, res.status, resource) + throw new Error('Unauthorized') } if (!res.ok) { - if (data.errorDetails) { - console.log(data.errorDetails) - } - throw new FetchError("Server Error", res.status) + showErrorToast(showToast, res.status, resource) + if (attempt === retries - 1) throw new Error(`HTTP error! status: ${res.status}`) + } else { + return await res.json() } - - return data // Successful fetch } catch (error) { if (isDev()) { - console.error( - `Error encountered for request:`, input, "with init:", init, "Error:", error - ) - } - // handle 403 errors without retries and show error toast - if ( - error instanceof FetchError && - error.status === 403 - ) { - showErrorToast(showToast, error.status, input) - throw error + console.error('Fetch error:', { input, init, error }) } - // Check if this is the final attempt - const isFinalAttempt = attempt === retries - 1 - const statusCode = error instanceof FetchError ? error.status : 0 - - // Show error toast on final attempt - if (isFinalAttempt) { - showErrorToast(showToast, statusCode, input) + if (attempt === retries - 1) { + showErrorToast(showToast, 0, resource) throw error } - - // Retry the fetch - await delay(retryDelay) - attempt++ } + + await new Promise(resolve => setTimeout(resolve, 1000)) } - throw new Error("Unexpected error: fetcher failed to return a response.") -} + + throw new Error('Fetch failed after all retries') +} \ No newline at end of file