kakasoo

MyAwaited, 프로미스 내부의 타입을 추론해서 꺼내기 본문

프로그래밍/TypeScript

MyAwaited, 프로미스 내부의 타입을 추론해서 꺼내기

카카수(kakasoo) 2023. 2. 25. 00:53
반응형
import type { Equal, Expect } from '@type-challenges/utils'

type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
type Z1 = Promise<Promise<Promise<string | boolean>>>
type T = { then: (onfulfilled: (arg: number) => any) => any }

type cases = [
  Expect<Equal<MyAwaited<X>, string>>,
  Expect<Equal<MyAwaited<Y>, { field: number }>>,
  Expect<Equal<MyAwaited<Z>, string | number>>,
  Expect<Equal<MyAwaited<Z1>, string | boolean>>,
  Expect<Equal<MyAwaited<T>, number>>,
]

// @ts-expect-error
type error = MyAwaited<number>
type MyAwaited <T extends PromiseLike<any>> = T extends PromiseLike<infer R> ? R : T;

// NOTE : 여전히 에러가 나는 케이스
// Expect<Equal<MyAwaited<Z>, string | number>>
// Expect<Equal<MyAwaited<Z1>, string | boolean>>

infer R을 이용해서, 만약 PromiseLike 타입이라면, Promise 내부의 타입을 반환하고,

PromiseLike 타입이 아닌 경우에는 처음에 받았던 걸 그대로 내보내준다.

 

type MyAwaited <T extends PromiseLike<any>> = T extends PromiseLike<infer R> ? R extends PromiseLike<any> ? MyAwaited<R> : R : T;

Promise가 중첩으로 감싸진 케이스를 커버하기 위해서 타입을 재귀적으로 감싼다.

두 번의 extends를 통해 아직 Promise가 남아 있을 경우 한 번 더 MyAwaited로 감싼다.

이 때, T는 처음부터 최소한의 타입으로 PromiseLike<any> 였기 때문에, 마지막 조건처럼 T가 반환되는 건 불가능하다.

즉, T는 never로 바꾸어도 된다.

 

type MyPromise<T> = Promise<T> | { then: (onfulfilled: (arg: T) => any) => any };
type MyAwaited <T extends MyPromise<any>> = T extends MyPromise<infer R> ? R extends MyPromise<any> ? MyAwaited<R> : R : never;

만약 PromiseLike를 사용하지 않는다면, MyPromise를 직접 구현하면 된다.

반응형