Last active
September 30, 2023 17:30
-
-
Save johnhunter/ccf5cf63d146975e894cbc8bb71cecc9 to your computer and use it in GitHub Desktop.
Augmenting native fetch to add error state handling and types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import HttpError from './HttpError'; | |
type FetchArgs = Parameters<typeof globalThis.fetch>; | |
/** | |
* Augments native fetch with error handling and typed response data. | |
*/ | |
export const fetch = async <RData = unknown>( | |
input: FetchArgs[0], | |
init: FetchArgs[1] = {}, | |
): Promise<RData> => { | |
const initWithDefaults = { | |
...init, | |
method: 'GET', | |
headers: { 'Content-Type': 'application/json' }, | |
}; | |
let res: Response; | |
let resJson: RData; | |
try { | |
res = await globalThis.fetch(input, initWithDefaults); | |
resJson = (await res.json()) as RData; | |
} catch (err: unknown) { | |
throw new HttpError(undefined, { cause: err as Error }); | |
} | |
if (res.ok) { | |
return resJson; | |
} else { | |
throw new HttpError(res); | |
} | |
}; | |
export const getJson = <RData = unknown>(path: string): Promise<RData> => | |
fetch<RData>(`${API_ROOT}${path}`); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const getErrorMessage = (response?: Response): string => { | |
const fallbackMsg = 'an unknown error'; | |
if (!response) { | |
return fallbackMsg; | |
} | |
const code = typeof response.status === 'number' ? response.status : ''; | |
const status = `${code} ${response.statusText || ''}`.trim(); | |
return status ? `status code ${status}` : fallbackMsg; | |
}; | |
interface ErrorOptions { | |
/** | |
* Expect cause to be an Error instance | |
*/ | |
cause: Error; | |
} | |
/** | |
* HttpError provides a single error class to handle all failure responses | |
* from the fetch request. | |
*/ | |
class HttpError extends Error { | |
public status: number | undefined; | |
constructor(response?: Response, options?: ErrorOptions) { | |
let errorMsg = 'an unknown error'; | |
if (options?.cause.name === 'SyntaxError') { | |
errorMsg = 'an invalid json response'; | |
} else { | |
errorMsg = getErrorMessage(response); | |
} | |
super(`Request failed with ${errorMsg}`, options); | |
this.name = 'HttpError'; | |
this.status = response?.status; | |
} | |
} | |
export default HttpError; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Simple implementation that will work in the browser or node>=18. When called you should provide a type param for the success payload. The error type extends HttpError but only status and statusText are guaranteed.