20 - Promise.all
- #Type Challenges
- #TypeScript
Question
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.
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
// expected to be `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const)
// !collapse(1:20) collapsed
/* _____________ 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 promiseAllTest5 = PromiseAll<(number | Promise<string>)[]>([1, 2, Promise.resolve('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[]>>>,
Expect<Equal<typeof promiseAllTest5, Promise<(number | string)[]>>>,
]선행 지식
-
Variadic Tuple
[...T]배열 리터럴을 튜플처럼 추론시키는 파라미터 패턴
const a = [1, 2, 3]; // 배열 리터럴 number[] // [...T] 형태의 파라미터 패턴을 사용하여 튜플로 추론하도록 유도 declare function f<T extends readonly unknown[]>(x: readonly [...T]): T; const t = f([1, 2, 3]); // [number, number, number] (환경에 따라 튜플 유지) // as const면 확정 튜플 const t2 = f([1, 2, 3] as const); // [1, 2, 3] -
TypeScript에서 배열/튜플 순회
T[number]는 “요소 타입”을 꺼내는 방식이라, 튜플에 쓰면 각 자리 정보가 사라지고 유니온으로 뭉개진다. 반대로 각 자리(0, 1, 2 …)를 유지하며 변환하려면 Mapped Type으로 keyof T를 순회한다.
type Input = ["A", "B", "C"]; // 1. T[number] : 튜플을 깨고 요소들을 유니온으로 뭉침 type AsUnion = Input[number]; // 결과: "A" | "B" | "C" // 2. T[number][] : 뭉쳐진 유니온을 다시 가변 길이 배열로 만듦 type AsArray = Input[number][]; // 결과: ("A" | "B" | "C")[] (길이 정보 사라짐) // 3. [K in keyof T] : 튜플 구조와 길이를 그대로 유지하며 순회 type AsMapped = { [K in keyof Input]: Input[K] }; // 결과: ["A", "B", "C"] (원래의 튜플 형태 유지) -
조건부 타입에서 유니온의 분배(분배 조건부 타입)
조건부 타입(
T extends U ? X : Y)에 유니온 타입이 전달될 때, 제네릭T가 아무런 가공도 되지 않은 순수한 상태(Naked Type Parameter)일 때만 유니온의 각 요소가 개별적으로 분배되어 평가된다. 그렇지 않은 경우 유니온이 통째로 평가된다.type ToArray<T> = T extends any ? T[] : never; type ToArrayNonDist<T> = [T] extends [any] ? T[] : never; // 1. 분배 발생: 순수한 타입 T를 그대로 사용 type Distributed = ToArray<string | number>; // 결과: string[] | number[] (각각 쪼개서 처리됨) // 2. 분배 방지: T를 [T]처럼 인덱싱/튜플로 감싸는 별개의 처리를 추가 type NonDistributed = ToArrayNonDist<string | number>; // 결과: (string | number)[] (통째로 평가됨)