Skip to content

Instantly share code, notes, and snippets.

@yuya-takeyama
Last active December 24, 2022 09:41
Show Gist options
  • Save yuya-takeyama/e4b9c2c5f85387734c39d4a44e674f8a to your computer and use it in GitHub Desktop.
Save yuya-takeyama/e4b9c2c5f85387734c39d4a44e674f8a to your computer and use it in GitHub Desktop.
Handle Response Error of JSON API in TypeScript (using async/await)
import fetch, { Response } from 'node-fetch';
interface ResponseWithParsedJson extends Response {
parsedJson?: any;
}
const toResponseWithParsedJson = (
res: Response,
json: any,
): ResponseWithParsedJson => {
const _res = res.clone() as ResponseWithParsedJson;
_res.parsedJson = json;
return _res;
};
export type FetchError =
| NetworkError
| RequestError
| ServerError
| UnknownError;
export interface NetworkError extends Error {
isNetworkError: boolean;
}
export interface RequestError extends ErrorWithResponse {
isRequestError: boolean;
}
export interface ServerError extends ErrorWithResponse {
isServerError: boolean;
}
export interface UnknownError extends ErrorWithResponse {
isUnknownError: boolean;
}
export interface ErrorWithResponse extends Error {
response: ResponseWithParsedJson;
}
export const isFetchError = (err: FetchError | any): err is FetchError => {
return (
isNetworkError(err) ||
isRequestError(err) ||
isServerError(err) ||
isUnknownError(err)
);
};
export const isNetworkError = (
err: NetworkError | any,
): err is NetworkError => {
return !!(err instanceof Error && (err as NetworkError).isNetworkError);
};
export const isRequestError = (
err: RequestError | any,
): err is RequestError => {
return !!(err instanceof Error && (err as RequestError).isRequestError);
};
export const isServerError = (err: ServerError | any): err is ServerError => {
return !!(err instanceof Error && (err as ServerError).isServerError);
};
export const isUnknownError = (
err: UnknownError | any,
): err is UnknownError => {
return !!(err instanceof Error && (err as UnknownError).isUnknownError);
};
const toFetchError = (err: Error, res: ResponseWithParsedJson): FetchError => {
(err as ErrorWithResponse).response = res;
if (res.status >= 400 && res.status < 500) {
(err as RequestError).isRequestError = true;
return err as RequestError;
} else if (res.status >= 500 && res.status < 600) {
(err as ServerError).isServerError = true;
return err as ServerError;
} else {
(err as UnknownError).isUnknownError = true;
return err as UnknownError;
}
};
const ensureError = (err: Error | string | any): Error => {
if (err instanceof Error) {
return err;
} else if (typeof err === 'string') {
return new Error(err);
} else {
return new Error('Unknown error');
}
};
const hasJsonContent = (res: Response): boolean => {
const contentType = res.headers.get('Content-Type');
return !!(contentType && contentType.includes('application/json'));
};
const jsonErrorMessage = (json: any): string | undefined => {
if (typeof json === 'object' && typeof json.message === 'string') {
return json.message;
}
};
const parseJson = async (res: Response): Promise<ResponseWithParsedJson> => {
const json = await res.json().catch(e => {
// When failed to parse JSON even though Content-Type is JSON
throw toFetchError(
new Error(`JSON parse error: ${ensureError(e).message}`),
res,
);
});
if (res.ok) {
// With successful response (2xx or 3xx)
return Promise.resolve(toResponseWithParsedJson(res, json));
} else {
// With error response (4xx, 5xx or anything)
return Promise.reject(
toFetchError(
ensureError(jsonErrorMessage(json)),
toResponseWithParsedJson(res, json),
),
);
}
};
const parseIfJson = async (res: Response): Promise<ResponseWithParsedJson> => {
if (hasJsonContent(res)) {
return parseJson(res);
} else {
if (res.ok) {
// With successful response (2xx or 3xx)
return Promise.resolve(res);
} else {
// With error response (4xx, 5xx or anything)
return Promise.reject(
toFetchError(new Error(`HTTP ${res.status}: ${res.statusText}`), res),
);
}
}
};
const handleNetworkError = (err: Error) => {
(err as NetworkError).isNetworkError = true;
return Promise.reject(err);
};
// Global response handler
const API = {
async get(path: string) {
try {
const res = await fetch(`https://blog.yuyat.jp${path}`);
return parseJson(res);
} catch (err) {
return handleNetworkError(err);
}
},
};
(async () => {
// Application logic
try {
const res = await API.get('/archives/2533');
console.log('Success!');
console.log(res);
} catch (err) {
if (isNetworkError(err)) {
console.error(err);
} else if (isRequestError(err)) {
console.error(err);
} else if (isServerError(err)) {
console.error(err);
} else {
console.error(err);
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment