별건 아니지만 typescript환경에서 에러처리를 할 때, 저는 try catch를 사용합니다.
const request = async <T>(config: AxiosRequestConfig): Promise<T> => {
try {
const { data } = await appAxiosClient.request<T>({ ...config });
return data;
} catch (error) {
const { response } = error as unknown as AxiosError;
if (response) {
throw { status: response.status, data: response.data };
}
throw error;
}
};
catch 내부에서 사용되는 에러는 typescript에서 unknown으로 취급하기 때문에
당연하지만 다음처럼 타입을 지정해서 사용할 수 없습니다.
catch (error: {
status: string;
data: string;
})
throw 문을 이용해 에러 발생을 시킬 때 에러에는 그 어떤 타입도 들어갈 수 있기 때문에
throw 'What the!?'
throw 7
throw {wut: 'is this'}
throw null
throw new Promise(() => {})
throw undefined
에러 처리 함수를 만들어서 사용하려고 할 때 타입 지정을 명시적으로 할 수가 없습니다.
const reportError = (error: {
status: string;
data: string;
}) => {
Sentry.throwException(error)
}
type assertion을 사용해서 타입 에러를 방지할 수 있습니다.
const { response } = error as unknown as AxiosError;
좋은 방법인가라고 했을때 그렇다고 할 수는 없습니다.
as 문을 사용하는 것은 자연스러운 방법은 아닙니다.
as를 쓸거면 왜 typescript를 쓰지?
손으로 딱 집어서 구멍에 넣을거면 왜 갑옷을 입나요?
catch의 인수를 any로 해서 사용할 수 있지만 에러 객체를 나타내는 클래스를 만들면 instanceof를 사용해 타입을 추론할 수 있게 할 수 있습니다.
catch(err) {
if (err instanceof ValidationError) {
throw new ReadError("Validation Error", err);
} else {
throw err;
}
}
이 방법을 사용하면 컴파일러가 err에 대한 속성을 추론할 수 있기 때문에 타입 에러가 나지 않습니다.
하지만 이 방법을 사용하기 위해서는 ReadError, ValidationError와 같은 커스텀 에러 객체에 대한 정보를 throw 하는 쪽과 catch하는 쪽이 모두 가지고 있어야 합니다.
api 서버와 클라이언트 서버가 분리되어있는 경우에는 이 방법이 불가능하다는 것이죠.
type ErrorWithMessage = {
message: string
}
function isErrorWithMessage(error: unknown): error is ErrorWithMessage {
return (
typeof error === 'object' &&
error !== null &&
'message' in error &&
typeof (error as Record<string, unknown>).message === 'string'
)
}
클라이언트 사이드에서 에러를 처리하기 위해서는 어쨌거나 as를 사용하는 부분이 있을 수 밖에 없습니다.
fetch, axios의 response를 받아오는 과정에서 error를 api 래퍼 모듈에서 처리할 때 as 문을 사용할 수 밖에 없기 때문입니다.
api 리스폰스에 대한 에러를 처리할 때 커스텀 에러 타입을 사용하고 싶으면, api fetch를 하는 곳에서 미리 서버와 합의한 응답 코드와 응답 값에 따라 커스텀 에러 객체를 throw해주는게 제일 좋은 것 같습니다.
우왕 좋은 글 잘 읽었습니다~!