Skip to content

Instantly share code, notes, and snippets.

@johnhunter
Last active September 30, 2023 17:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johnhunter/ccf5cf63d146975e894cbc8bb71cecc9 to your computer and use it in GitHub Desktop.
Save johnhunter/ccf5cf63d146975e894cbc8bb71cecc9 to your computer and use it in GitHub Desktop.
Augmenting native fetch to add error state handling and types
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}`);
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;
@johnhunter
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment