Skip to content

Commit

Permalink
Refactor fetcher.ts to improve error handling and readability
Browse files Browse the repository at this point in the history
  • Loading branch information
dnlbui committed Jul 23, 2024
1 parent 29e1b18 commit 1a945b7
Showing 1 changed file with 26 additions and 81 deletions.
107 changes: 26 additions & 81 deletions hooks/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,107 +4,52 @@ import { isDev } from '../utils/is-dev'

const { apiBase } = useGlobals()

function delay(ms: number): Promise<void> {
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(`<span>${message}</span>`)
}

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(`<span>${baseMessage} ${reportMessage}</span>`)
}

export const fetcher = async <T>(
export async function fetcher<T>(
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<T> => {
let attempt = 0
while (attempt < retries) {
retries: number = 2
): Promise<T> {
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')
}

0 comments on commit 1a945b7

Please sign in to comment.