본문 바로가기
Language/Typescript

타입챌린지 : 20-Promise.all

by hsloth 2023. 3. 24.

 

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

 

https://github.com/type-challenges/type-challenges/blob/main/questions/00020-medium-promise-all/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

 

와.. 좀 많이 어려운 문제였다.

배열을 PromiseAll함수의 인자로 받아서 Promise<배열>을 리턴하는 함수이다.

 

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

declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<{ [K in keyof T]: MyAwaited<T[K]>}>

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

const promiseAllTest1 = PromiseAll([1, 2, 3] as const)
const promiseAllTest2 = PromiseAll([1, 2, Promise.resolve(3)] as const)
const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)])
const promiseAllTest4 = PromiseAll<Array<number | Promise<number>>>([1, 2, 3])
const a = [1,2,3]

type cases = [
  Expect<Equal<typeof promiseAllTest1, Promise<[1, 2, 3]>>>,
  Expect<Equal<typeof promiseAllTest2, Promise<[1, 2, number]>>>,
  Expect<Equal<typeof promiseAllTest3, Promise<[number, number, number]>>>,
  Expect<Equal<typeof promiseAllTest4, Promise<number[]>>>,
]

 

내가 처음으로 생각한 답을 봐보자.

declare function PromiseAll<T>(values: any): Promise<T>;

정말 하찮다. 어떻게 풀어야할지 모르겠어서 일단 되는대로 적었다. 그래도 얼추 틀은 맞았다. 여기서 몇가지만 추가해주면 된다.

 

일단, 저놈의 T를 어떻게 해야된다. 그래서 T를 끼워 맞추기 위해 다음과 같이 작성했다.

declare function PromiseAll<T extends any[]>(values: T): Promise<T>

 

 

PromiseAll은 배열을 인자로 받기 때문에 T를 배열이라고 제한하고, values를 T타입으로 명시해 주었다.

여기서 문제는 as const로 인자를 타입으로 받으면 에러가 난다는 것이다... ( [1,2,3] as const 는 [1,2,3] 자체를 타입으로 정하는 문법?이다)

 

그래서 아래와 같이 수정해주었다.

declare function PromiseAll<T extends any[]>(values: readonly T): Promise<T>

이번엔 values: readonly T에서 에러가 발생하기 시작했다. 이유를 알아보니... T는 그냥 any[] 타입이라 readonly를 설정할 수 없다는 것이었다. readonly는 배열 및 튜플의 리터럴에만 허용이 된단다... 쉽게 말하자면... T는 any[] 타입이라 그 자체로 타입이기 때문에 readonly 설정이 안되고, [...T] 같이 만들어 주어야 readonly를 설정해줄 수 있다는 것이다.

 

그래서 아래와 같이 코드를 수정했다.

declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<T>

여기서, Promise<T>를 그대로 리턴하면 안된다. 왜냐하면 함수의 인자로 Promise가 들어갔을 경우, Promise 안에 들어있는 값을 꺼내주어야 하기 때문이다.

ex) 테스트 케이스의 Promise.resolve(3)의 경우 Promise를 리턴하기 때문. Promise<[1, 2, Promise]> 를 리턴하면 안되니까...

 

그래서 아래와 같이 코드를 고칠 수 있다.

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

declare function PromiseAll<T extends any[]>(values: readonly [...T])
	: Promise<{ [K in keyof T]: MyAwaited<T[K]>}>;

타입스크립트에서 배열은 key값이 0, 1, 2... 인 객체기 때문에

[K in keyof T] 로 T의 키값인 0, 1, 2... 를 빼내고, 해당 K의 value를 MyAwaited<T[K]>로 정의한다.

MyAwaited는 Promise를 제거하는 타입이다. (Promise안에 들어있는 값을 빼내는 타입) 여기서 T[K]는 T배열에서 K라는 key의 value값을 뜻한다.

그리고 이걸 중괄호로 감싸서 해당 값이 객체라고 말해준다.

 

즉, T가 [1, 2, 3] 이라면

{ 0: 1, 1: 2, 2: 3} 인 객체가 되는 것이다.

 

MyAwaited 구현은 아래를 참고하자.

참고

easy난이도에서 MyAwaited 구현

https://suloth.tistory.com/31

 

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

이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다. https://github.com/type-challenges/type-challenges/blob/main/questions/00189-easy-awaited/READM

suloth.tistory.com

 

의문점

그렇다면 promiseAllTest2와 promiseAllTest3의 차이점이 뭐길래 리턴 값이 다른걸까?

const promiseAllTest2 = PromiseAll([1, 2, Promise.resolve(3)] as const)
const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)])

Expect<Equal<typeof promiseAllTest2, Promise<[1, 2, number]>>>,
Expect<Equal<typeof promiseAllTest3, Promise<[number, number, number]>>>,

여기서 차이점은 Test2는 as const를 인자로 넣어줬다는 점이고, Test3는 그냥 배열을 넣어줬다는 점이다.

무슨 차이일까?

그것은 바로 컴파일러가 ts파일을 컴파일할 때, 해당 인자를 어디까지 예상하느냐의 차이다.

 

PromiseAll([1, 2, Promise.resolve(3)] as const)의 경우

[1, 2, Promise.resolve(3)] 자체가 타입이기 때문에 컴파일 시, 배열에 1과 2와 Promise.resolve(3)이 정확하게 들어있다고 파악할 수 있다.

 

하지만, PromiseAll([1, 2, Promise.resolve(3)])의 경우

컴파일시 1, 2, Promise.resolve(3)을 그저 number라는 타입의 원소가 담긴 배열이라고 인식하기 때문에

[number, number, Promise<number>] 를 리턴하게 된다.