본문 바로가기
Language/Typescript

타입챌린지 : 189-Awaited (easy)

by hsloth 2023. 3. 3.

 

이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다.

 

https://github.com/type-challenges/type-challenges/blob/main/questions/00189-easy-awaited/README.md

 

GitHub - type-challenges/type-challenges: Collection of TypeScript type challenges with online judge

Collection of TypeScript type challenges with online judge - GitHub - type-challenges/type-challenges: Collection of TypeScript type challenges with online judge

github.com

 

 

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

// test cases
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>

PromiseLike는 Promise같은 객체들을 다루는 가리키는 타입이라고 생각하면 된다.

infer R은 변수를 지정하듯이 R이라는 타입 인자를 선언(초기화) 한다고 생각하자. 실제로... 그런 개념이랑은 좀 다른것 같지만.

 

처음에는

type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer R> ? MyAwaited<R> : never; 를 생각했었다.

그런데 MyAwaited<R> 에서 R이 빨간줄이 뜨는 것이다... 그래서 R을 PromiseLike 타입을 받을 수 있도록 하려고 이런 저런 시도를 해봤는데, 결국 삼항연산자를 하나 더 쓰는게 답이었다. 처음에는 infer R extends PromiseLike<any> 같은 시도도 해봤으나 문법 오류였다 ㅋㅋ

type MyAwaited : MyAwaited 타입을 정의한다

<T extneds PromiseLike<any>> : T는 any 타입을 리턴하는 PromiseLike 형태이다 라는 뜻

조건 1. T extends PromiseLike<infer R> ? : 만약 T가 R이라는 타입을 리턴 타입으로 가지는 PromiseLike 이라면

조건 2. R extends PromiseLike<any> ? : 만약 R이 PromiseLike<any> 타입이라면 (재귀 형태를 띄기 때문에 R을 PromiseLike<any>로 추론하게 하려면 이런 조건이 들어가야한다)

MyAwaited<R> : R : never : 조건 1이 참이라면 MyAwaited<R>을 리턴, 조건 2가 참이라면 R을 리턴, 그 이외는 never을 리턴한다.

 

복잡하게 보이지만 생각보다 간단하다.

위의 해석이 복잡하다고 생각되는 사람은 이 구문을 먼저 봐보자.

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

위 구문을 이해했다면, error를 발생시켜야하는 case인 MyAwaited<number>가 에러를 발생시키지 않는 다는 것을 깨닫게 될 것이다.

그렇다면, 제네릭 인자로 number를 받았을 때 에러를 뱉게 해야하는데, 이 방법이 바로 T에 PromiseLike를 상속하는 것이다.

따라서, 

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

하지만 여기서 MyAwaited<R> 에서 에러가 난다. MyAwaited는 PromiseLike를 제네릭으로 받는데, R이 PromiseLike 타입이 아니기 때문이다.

그래서 R이 PromiseLike 이다 라는 조건을 추가해주면

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

라는 답이 나온다.