본문 바로가기
source-code/TypeScript

JsToTs : 비동기 처리 - Promise

by mattew4483 2023. 8. 21.
728x90
반응형

JavaScript를 TypeScript로 변환하기🦾

 

비동기 처리

유저 정보를 받아오는 비동기 처리 함수를 생각해보자.

 

기존 JS 코드는 다음과 같다.

const getUserProfile = async ({ id }) => {
  let config = {
    method: "get",
    url: `${URL}/auth/profile/${id}`,
  };

  try {
    const _res = await axios.get(`${URL}/auth/profile/${id}`);
    return _res.data;
  } catch (e) {
    throw e;
  }
};

항상 기억하자 → TS는 JS의 변수, 매개 변수, 리턴값에 타입을 지정한 것!

 

우선 가장 먼저 눈에 띄는(그리고 선명한 빨간 밑줄이 그여있는) 매개변수의 타입을 지정해주자.

const getUserProfile = async ({ id }: { id: number }) => {
  let config = {
    method: "get",
    url: `${URL}/auth/profile/${id}`,
  };

  try {
    const _res = await axios.get(`${URL}/auth/profile/${id}`);
    return _res.data;
  } catch (e) {
    throw e;
  }
};

에러가 사라졌다. 성공!!!

...인 줄 알았지만... 위 함수에 마우스를 올려보면...

해당 함수의 리턴값이 Promise<any>임을 확인할 수 있다.

async 키워드로 인해 getProfile 함수는 항상 Promise를 return할테니, Promise로 자동 타입 추론이 이뤄진 건 당연하지만...

 

any 타입 =  어떠한 타입도 할당이 가능하다 → 타입 추론을 포기하겠다 → TS를 사용하는 의미가 없다!!!

즉 getUserProfile 함수의 리턴값에 대한 타입을 좀 더 명시해줄 필요가 있겠다.

 

그런데 일단 Promise란 interface의 첫번째 제네릭은 어떤 의미지?

interface Promise<T> {
    /**
     * Attaches callbacks for the resolution and/or rejection of the Promise.
     * @param onfulfilled The callback to execute when the Promise is resolved.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of which ever callback is executed.
     */
    then<TResult1 = T, TResult2 = never>(
        onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, 
        onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
    ): Promise<TResult1 | TResult2>;

    /**
     * Attaches a callback for only the rejection of the Promise.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of the callback.
     */
    catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}

아하!

Promise라는 interface는 then, catch라는 메서드를 가지고 있으며,

이 때 제네릭 T는 onfulfilled callback의 매개변수 value의 타입 이자,

onfulfilled callback의 리턴값의 타입(TResult1 = T)을 의미했다!

→ 결론적으로... Promise의 제네릭은 해당 Promise가 fulfilled 됬을 때 반환받는 데이터 타입을 의미한다!

 

즉 getUserProfile함수의 리턴값인 Promise<any>는, fulfilled시 any 타입을 리턴하는 Promise를 뜻하는 것!

따라서... 외부에서 해당 비동기 요청의 결과 타입을 추론할 때는 'any 타입의 무언가가 반환됨' 그 이상은 알 수 없다. 이런!

 

그렇다면 해결 방안은? → 리턴값의 타입을 좁혀주면 될 일.

getUserProfile 함수의 리턴값은 id에 해당되는 유저의 정보일테니...

interface UserInfo {
  id: number;
  name: string;
}

const getUserProfile = async ({ id }: { id: number }): UserInfo => {
  let config = {
    method: "get",
    url: `${URL}/auth/profile/${id}`,
  };

  try {
    const _res = await axios.get(`${URL}/auth/profile/${id}`);
    return _res.data;
  } catch (e) {
    throw e;
  }
};

UserInfo란 Interface를 선언한 후, getUserProfile 함수의 리턴값으로 타이핑 해줬다.

 

그럼 곧바로 에러를 만나는데...

앞서 작성한 것 처럼 async 키워드로 인해 해당 함수는 항상 Promise 객체를 리턴한다.

그런데 우리는 리턴값을 UserInfo로 타이핑 해줬으니 에러가 날 수 밖에!

interface UserInfo {
  id: number;
  name: string;
}

const getUserProfile = async ({ id }: { id: number }): Promise<UserInfo> => {
  let config = {
    method: "get",
    url: `${URL}/auth/profile/${id}`,
  };

  try {
    const _res = await axios.get(`${URL}/auth/profile/${id}`);
    return _res.data;
  } catch (e) {
    throw e;
  }
};

따라서 Promise interface의 예상 반환값의 타입, 즉 UserInfo를 제네릭으로 넘겨주면

우리가 원하는 형태로 타입 추론이 이뤄짐을 확인할 수 있다.

 

 

 

 

 

 

728x90
반응형