개발새발
Promise, Awaited 타입 분석하기 본문
JavaScript에서 Promise는 비동기 처리를 위한 기본 객체다. TypeScript는 이 Promise가 반환하거나 체이닝하는 값을 정적으로 추론할 수 있도록 복잡하고 정교한 타입 시스템을 갖추고 있다. 이번 글에서는 Promise, Awaited, 그리고 Promise.all, .then(), .catch()가 어떻게 타입 추론을 수행하는지를 분석해본다.
Promise.resolve vs await
const str1 = Promise.resolve('promise'); // Promise<string>
const str2 = await Promise.resolve('promise'); // string
- Promise.resolve는 Promise<T>를 반환한다.
- await는 Awaited<T>로 타입이 바뀐다.
이때 Awaited<T>의 정의를 살펴보면 다음과 같다
type Awaited<T> =
T extends null | undefined ? T :
T extends object & { then(onfulfilled: infer F, ...args: any): any } ?
F extends (value: infer V, ...args: any) => any ?
Awaited<V> :
never :
T;
규칙 요약
- 규칙 1: 객체가 아닌 타입(string, number, boolean)은 그대로 반환.
- 규칙 2: Promise<T> 타입은 내부의 T로 재귀적으로 들어가면서 Awaited<T> 처리.
즉, Awaited<Promise<Awaited<string>>> → Awaited<string> → string.
Promise.all의 타입 추론
const all = await Promise.all([
'string',
Promise.resolve(123),
Promise.resolve(Promise.resolve(true)),
]); // [string, number, boolean]
타입 정의
all<T extends readonly unknown[] | []>(
values: T
): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;
- 각 요소 T[P]에 대해 Awaited를 적용하고,
- 배열 그대로 매핑하여 [string, number, boolean]이라는 정확한 튜플 타입을 반환한다.
중첩된 Promise<promise>도 자동으로 Awaited가 적용되어 T가 추출된다.
then 체이닝의 타입 추론
const chaining = await Promise.resolve('hi')
.then(() => 123)
.then(() => true)
.catch((err) => {
console.error(err);
}); // boolean | void
- Promise.resolve('hi') → Promise<string>
- 첫 번째 .then(() => 123) → Promise<number>
- 두 번째 .then(() => true) → Promise<boolean>
- .catch()가 반환하는 값은 void이므로, 최종 타입은 Promise<boolean | void>
- await을 붙이면 boolean | void
관련 타입 선언
interface Promise<T> {
then<TResult1 = T, TResult2 = never>(
onfulfilled?: (value: T) => TResult1 | PromiseLike<TResult1>,
onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>
): Promise<TResult1 | TResult2>;
catch<TResult = never>(
onrejected?: (reason: any) => TResult | PromiseLike<TResult>
): Promise<T | TResult>;
}
구조적 타이핑과 thenable 객체
const thenable = {
then(onfulfilled: (v: number) => void) {
setTimeout(() => onfulfilled(42), 10);
},
};
const v = await thenable; // number
- 객체가 then(onfulfilled) 메서드를 가지고 있으면, TypeScript는 이를 PromiseLike로 간주한다.
- Awaited<T>는 실제 Promise 인스턴스가 아니더라도 해당 구조만 맞으면 thenable 객체로 처리한다.
정리
상황 | 결과 타입 | 근거 설명 |
await Promise.resolve('a') | string | Awaited<Promise<T>> → Awaited<T> |
Promise.all([...]) | 튜플 형태로 정확 추론 | 각 요소에 Awaited 적용 |
then().then() | 마지막 then 반환값 | 이전 then의 결과 타입 기반 추론 |
.catch() | 실패 시 반환 타입 포함 | TResult 추가 → 성공 |
await thenableObject | then의 인수 타입 추론 | 구조적 타이핑으로 PromiseLike 처리 |
Promise의 타입은 단순한 타입이 아니다. 특히 Awaited, then 체이닝, Promise.all 등에서는 복잡한 조건부 타입과 재귀적인 타입 추론이 동작한다.
Awaited<T>를 분석해보면, 타입스크립트가 비동기 값을 어떻게 정적으로 분석하고 추적하는지를 깊이 이해할 수 있다.
구조적 타이핑 덕분에 실제 Promise 인스턴스가 아니어도 thenable이면 await할 수 있다는 점도 기억해두자.
'Typescript' 카테고리의 다른 글
tsc와 tsconfigjson (0) | 2025.05.10 |
---|---|
bind 분석하기 (0) | 2025.05.10 |
flat 분석하기 (0) | 2025.05.10 |
reduce 만들기 (0) | 2025.04.30 |
filter 만들기 (0) | 2025.04.30 |