별 건 아니지만 typescript 에러 핸들링에 대해

dante Yoon·2022년 4월 24일
47

별 건 아니지만

목록 보기
1/11
post-thumbnail

별건 아니지만 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)
}

workaround

type assertion

type assertion을 사용해서 타입 에러를 방지할 수 있습니다.

const { response } = error as unknown as AxiosError;

좋은 방법인가라고 했을때 그렇다고 할 수는 없습니다.

as 문을 사용하는 것은 자연스러운 방법은 아닙니다.

as를 쓸거면 왜 typescript를 쓰지?
손으로 딱 집어서 구멍에 넣을거면 왜 갑옷을 입나요?

instanceof Error

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해주는게 제일 좋은 것 같습니다.

profile
성장을 향한 작은 몸부림의 흔적들

9개의 댓글

comment-user-thumbnail
2022년 4월 25일

우왕 좋은 글 잘 읽었습니다~!

1개의 답글
comment-user-thumbnail
2022년 4월 25일

짤이 너무 웃기네요ㅋㅋㅋㅋㅋ 잘 읽었습니다 !

1개의 답글
comment-user-thumbnail
2022년 4월 26일

이 글을 읽고 타입스크립트랑 친해졌어요

1개의 답글
comment-user-thumbnail
2022년 4월 29일

짤보고 들어왔다가 좋은 정보 얻고 갑니다 :)

1개의 답글