Skip to content

Instantly share code, notes, and snippets.

@yuya-takeyama
Last active April 21, 2022 05:50
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save yuya-takeyama/2a18d166b43101ca77a461fded8916c9 to your computer and use it in GitHub Desktop.
Save yuya-takeyama/2a18d166b43101ca77a461fded8916c9 to your computer and use it in GitHub Desktop.
Handle Response Error of JSON API in TypeScript
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 = (res: Response): Promise<ResponseWithParsedJson> => {
return new Promise((resolve, reject) => {
res
.json()
.catch(e => {
// When failed to parse JSON even though Content-Type is JSON
reject(
toFetchError(
new Error(`JSON parse error: ${ensureError(e).message}`),
res,
),
);
})
.then(json => {
if (res.ok) {
// With successful response (2xx or 3xx)
resolve(toResponseWithParsedJson(res, json));
} else {
// With error response (4xx, 5xx or anything)
reject(
toFetchError(
ensureError(jsonErrorMessage(json)),
toResponseWithParsedJson(res, json),
),
);
}
});
});
};
const parseIfJson = (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 = {
get(path: string) {
return fetch(`https://blog.yuyat.jp${path}`)
.catch(handleNetworkError)
.then(parseIfJson);
},
};
// Application logic
API.get('/archives/2533')
.then(res => {
console.log('Success!');
console.log(res);
})
.catch((err: FetchError | Error) => {
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