타입스크립트 제네릭으로 Promise 다뤄보기

Hushed_Mind·2025년 6월 28일

TypeScript

목록 보기
9/10
post-thumbnail

하아.. 일주일마다 쓰려고 했는데 또 늦어버렸다.. 그래도 꾸준히 쓰려는 마음이 더 중요한거 아닐까? ㅎㅎㅎ.. 쨋든 오늘도 타입스크립트 관련해서 써보려고 한다.

Promsie 다루어보기

처음엔 단순히 비동기 함수를 만들 때 Promise<any> 같은 식으로 다 썼다. 그냥 값이 나중에 오니까 그거 받고 .then() 붙이거나 await 쓰면 되겠지 싶었다. 근데 프로젝트 규모가 커질수록 이런 애매한 타입들이 점점 문제를 일으키기 시작했다.

이 함수가 뭘 반환하는지를 명확히 알 수 없으니, await로 받아도 타입 추론이 안되고, 결국 또 타입을 단언하거나 따로 주석으로 적는 일이 많아졌다. 이건 타입스크립트를 쓰는 이유랑 거리가 멀어 보였다.

그래서 알게 된 것이 제네릭을 활용한 Promise 타입 지정이다.


왜 any 는 부족한가..?

예를 들어서 아래와 같은 API 호출 함수가 있다고 해보자.

function fetchData(url: string): Promise<any> {
	return fetch(url).then(res => res.join());
}

표면적으로는 문제가 없어 보인다. 하지만 실제로 await fetchData(...)를 한 그다음 줄 부터는 그 갑싱 어떤 구조인지 IDE도 모르고, 나도 확신할 수 없다. 타입스크립트를 쓰는 이유가 그 "확신"을 갖기 위함인데, 이 코드에는 그게 없다..!


제네릭으로 타입을 주입받게 만든다면?

그래서 아래처럼 바꿔본다.

function fetchData<T>(url: string): Promise<T> {
	return fetch(url).then(res => res.join());
}

이제 이 함수는 내가 직접 예상하는 타입을 명시적으로 지정할 수 있게 된다.

type User = {
	id: number;
  	name: string;
}

const getUser = async () => {
	const user = await fetchData<User>('/api/user/1');
  	console.log(user.name); // <- 타입이 보장됨
}

진짜 중요한건 여기서 fetchData 함수가 User 타입을 반환한다는 약속을 함수 정의가 아니라 호출부에서 직접 명시했다는 점이다. 이건 엄청나게 큰 차이다. 호출하는 사람이 의도를 명확히 표현할 수 있고, 타입 추론까지 자연스럽게 따라오게 된다.


T를 활용한 응답 구조 커스터마이징

더 나아가면, 요즘 흔히 쓰는 API 구조처럼 응답이 다음과 같이 생겨 있을 수 있다.

{
	"success" : true,
  	"data" : {
    	"id" : 1,
      	"name" : "Lee"
    }
}

이런 경우에 일반화를 하고 싶다면?

type ApiResponse<T> = {
	success: boolean;
  	data: T;
};

function fetchApi<T>(url:string): Promise<ApiResponse<T>> {
	return fetch(url).then(res => res.json());
}

이렇게 하면 어떤 데이터든 data 안에 들어간다는 구조를 유지하면서도 T 를 통해 유연하게 타입을 바꿀 수 있다.

type Product = {
	id: number;
  	price: number;
};

const getProduct = async () => {
	const response = await fetchApi<Product>('/api/product/1');
  	if (response.success) {
    	console.log(response.data.price); // <- 타입에 안전하게 접근 가능.
    }
}

이걸 써보면 느끼는게 있다.
제네릭은 단순히 타입을 뿌옇게 만드는 게 아니라, 타입을 더 정확하게 유지하면서도 확장성을 높여주는 방식이다.


제네릭 Promise는 타입을 연결하는 다리다!

정리하자면, 타입스크립트에서 Promise<T> 는 단순히 "나중에 올 거야"를 표현하는게 아니라,
"이 값이 어떤 타입인지 정확하게 예측할 수 있어"라는 확신을 유지한 채 비동기 흐름을 다루는 도구가 된다.

그리고 이걸 제네릭으로 만들면, 다음과 같은 이점이 생긴다

  • API 응답마다 구조가 달라도 T 하나로 통일된 패턴 유지 가능
  • 호출하는 쪽에서 의도를 명확하게 드러낼 수 있다.
  • 타입 추론이 자연스럽게 따라와서 실수를 줄인다.
  • 리팩토링을 할 때도 타입이 따라오므로 유지보수가 수월하다.

결론

처음엔 Promise<T>처럼 생긴 문법이 복잡하고 과하다고 느껴질 수 있다.
하지만 프로젝트를 계속 진행을 하다 보면, "이 함수가 어떤 데이터를 반환하지?" 하는 질문이 계속 머릿속에 맴돈다..

그럴 때마다 나는 Promsie<any> 대신 Promise<T> 를 고민해본다.
"이 데이터가 어떤 타입이어야 하고, 그게 함수 호출부에까지 드러나야 하지 않을까?" 라는 생각 말이다.

다음에는 타입 조작이나 조건부, 유틸리티 타입에 대해 알아보려고 한다
그러면 일주일동안 퇴근하고 공부를 해야겠지..ㅠㅠ 열심히 해보자!!

profile
개발 공부 블로그

0개의 댓글