My Boundary As Much As I Experienced

AxiosInstance의 반환값은 AxiosResponse<T>라는데 왜 사용하면서 반환값으로 Promise<T>만 써왔을까? 본문

FrontEnd/TypeScript

AxiosInstance의 반환값은 AxiosResponse<T>라는데 왜 사용하면서 반환값으로 Promise<T>만 써왔을까?

Bumang 2024. 6. 6. 18:00

 

궁금해진 계기

어제 백엔드 개발자 분이 주신 데이터를 보고 status, data 부분이 계속 반복된다는걸 깨닫고 ResponseType이란걸 만들어서 재사용하려고 했다.

type ResponseType<T> = {
    status: T,
    data: ...
}

 

 

그리고 여느때와 마찬가지로 API함수 안에서 AxiosInstance를 사용해서 호출한 값을 반환하려 했다.

그리고 Promise<ResponseType<T | false>>를 타입지정해주려 했는데

res에는 data라는 컬럼이 없다는 에러가 나왔다.

 

export const getAbuserData = async (id: string): Promise<ResponseType<AbuserDataResponse | false>> => {
    if (!id) return false

    const res = await API.get(`/abuser/${id}`);

    return res.data.result;
};

 

API명세서에는 분명 있는데 왤까.. 그런데 ResponseType을 지우고

Promise<T | false>만 사용하니 제대로 호출이 되고 타입지정도 잘 되는걸 볼 수 있었다.

 

그러고보니 나는 지금까지 백엔드에서 주는 명세서에는 data와 status 필드가 다 있었는데

Promise<T>를 사용할 때 data에 있는 데이터만 T를 사용해서 지정했지

data, status 필드를 다 포함하는 wrapping type을 쓴 적은 없었다.

 

 

기본적으로 Axios로 호출 후 반환된 값들은 모두 AxiosResponse<T>로 맵핑되어있다.

그래서 찾아보니 Axios로 호출하여 반환한 값들은 모두 기본적으로 AxiosResponse<T>가 맵핑되어있다고 한다.

그러므로 내가 Promise<ResponseType<T | false>>로 지정했던게
사실 Promise<AxiosResponse<ResponseType<T | false>>>로 처리되고 있던 거였다.

export const getAbuserData = async (id: string): Promise<ResponseType<AbuserDataResponse | false>> => {
    if (!id) return false

    const res = await API.get(`/abuser/${id}`);

    return res.data.result;
};

 

 

AxiosResponse<T>가 생략되어있다면 굳이 다 표기하는 것은 가능한가?(Promise<AxiosResponse<T>>)

나는 지금까지 작업하면서 AxiosResponse<T>가 기본적으로 맵핑되어 있는지 전혀 몰랐다.. 그렇다면 AxiosResponse를 생략하지 않고 다 표기하는 것은 가능한가?

 

결론은 가능하다. 그러나 이렇게 표기하면 AxiosInstance를 거치지 않은 반환값을 리턴할 때 AxiosResponse에 담은 

(예를 들어 패러미터가 undefined이면 early return을 해야될 때)

'T는 AxiosResponse<T>에 포함되지 않는다'는 식의 에러를 경험할수도 있다.

export const getAbuserData = async (id: string): Promise<AxiosResponse<AbuserDataResponse | false>> => {
    if (!id) return false // Type 'boolean' is not assignable to type 'AxiosResponse<false | AbuserDataResponse, any>'.ts(2322)

    const res = await API.get(`/abuser/${id}`);

    return res.data.result;
};

 

나는 일례로 id값이 혹여 없다면 false를 바로 내려주게 하려고 했다.

그런데 Promise<T | false>가 아니라 AxiosResponse<T | false>로 하니

이걸 아래 예시처럼 AxiosResponse<T>에 맞는 형태로 꼭 리턴해야되는 번거로움이 발생했다.

export const getAbuserData = async (id: string): Promise<AxiosResponse<AbuserDataResponse | false>> => {
    if (!id) {
        return {
            data: false,
            status: 400,
            statusText: 'Bad Request',
            headers: {},
            config: {},
        } as AxiosResponse<false>; // 필요한 최소한의 AxiosResponse 구조를 반환
    }

    const res = await API.get(`/abuser/${id}`);

    return res.data.result;
};

 

그러므로 그냥 AxiosResponse를 명시해줘서 힘들게 코드량을 늘릴 필요는 없을거 같다!

 

 

Axios가 아니라 fetch API를 사용할 때는?

이럴 때는 status, data등을 다 표기한 wrapper type을 사용하는게 편리할수도 있다.

interface ResponseType<T> {
  data: T;
  status: number;
  statusText: string;
}

const getData = async <T>(url: string): Promise<ResponseType<T>> => {
  try {
    const response = await fetch(url);

    // 응답이 성공적인지 확인
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    // JSON 형태로 변환
    const data: T = await response.json();

    // 상태 코드와 데이터를 함께 반환
    return {
      data,
      status: response.status,
      statusText: response.statusText
    };
  } catch (error) {
    console.error('Fetch error:', error);
    throw error;
  }
};

 

역시 여러모로 Axios를 사용해서 편했던 이유를 알게되었고 앞으로도 자주 이용하자는 생각이 드는 스터디였다.