Type the function PromiseAll that accepts an array of PromiseLike objects, the returning value should be Promise<T> where T is the resolved result array.
PromiseLike 객체 배열을 인자로 받고,
반환 값이 Promise<T>에서 resolved된 결과 타입 T인 타입 배열을 반환하는
함수를 PromiseAll을 작성하시오.
type Resolved<T extends any>=
T extends PromiseLike<infer K>?
K
:T
type PromiseAllReturnType<T extends any[],Result extends any[]=[]>=
T extends [infer First,...infer Rest]?
PromiseAllReturnType<Rest,[...Result,Resolved<First>]>
:Result['length'] extends 0?
T extends (infer R)[]?
Promise<Resolved<R>[]>
:never
:Promise<Result>
declare function PromiseAll<T extends any[]>(values: T): PromiseAllReturnType<T>
해결 방식은
1) 튜플 타입이면 일일히 Result에다가 Resolved를 이용한 타입을 재귀적으로 넣어줌
2) 튜플타입이 아닌 경우(Result의 길이가 0인 경우) 그냥 바로 Resolved한 배열로 넣어줌
이었다.
일반적으로는 해결되나
뒤에서 두번째 예시는 해결되지 못했다
const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)])
Expect<Equal<typeof promiseAllTest3, Promise<[number, number, number]>>>
문제는 함수 인자에 as const가 붙지 않았을 때 함수의 제네릭을 제대로 파악하지 못하는게 문제였다
(PromiseAll([1,2,Promise.resolve(3)]) 의 제네릭을 [number,number,Promise<number>]로 파악하지 못하고
(number|Promise<number>)[]로 파악함)
이 곳에서 많은 논의가 오갔다
declare function PromiseAll<T extends any[]>(values: readonly [...T]):
Promise<{ [K in keyof T]: T[K] extends Promise<infer R> ? R : T[K] }>;
함수 선언 부에서 mapped type을 이용해 간단하게 작성한 점이 인상깊었다
또, 내가 지적한 as const의 문제점을 [...T]로 해결한 것도 인상깊었다.
내 코드의 인자 부분을 다음과 같이 바꾸니 제네릭을 제대로 읽지 못하는 문제가 해결되었다.
다만 이 코드는 튜플이 아닌 number[]형과 같은 배열 타입에 대한 문제는 해결하지 못했다.
이 코드를 조금 더 간결하게 작성해
declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<{
[key in keyof T]: Awaited<T[key]>
}>
와 같은 코드를 작성한 사람도 있었다.
내가 만든 Resolved를 조금 더 잘 정의한 유틸리티 타입인 듯하다.
declare function PromiseAll<T extends any[]>(p: readonly [...T]): Promise<{
[key in keyof T]:Awaited<T[key]>
}>
이 코드가 문제를 잘 해결한 코드인 것 같다
https://github.com/type-challenges/type-challenges/issues/211
https://www.typescriptlang.org/docs/handbook/utility-types.html#awaitedtype