Завдання

Типізувати функцію PromiseAll, яка приймає масив PromiseLike об’єктів і повертає Promise<T>, де T, це масив типів результату виконання Promise.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
  setTimeout(resolve, 100, "foo");
});

// expected to be `Promise<[number, number, string]>`
const p = Promise.all([promise1, promise2, promise3] as const);

Розв’язок

Почнемо з простого – функція що повертає Promise<T>.

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

Тепер треба придумати, як вирахувати типи з виконаних Promise. Почнемо з факту, що values це масив. Виразимо це в наших типах.

Використовуючи варіативні типи вказуємо, що values це масив, а T елементи цього масиву:

declare function PromiseAll<T extends unknown[]>(values: [...T]): Promise<T>;

Отримуємо помилку “Argument of type ‘readonly [1, 2, 3]’ is not assignable to parameter of type ‘[1, 2, 3]’.“. Тому що values не очікує модифікатор readonly в параметрі. Виправимо це, додавши модифікатор до параметра функції:

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

В нас є рішення, яке проходить один з тестів. Це тому, що цей тест не містить Promise. Ми повертаємо такий самий масив, який ми отримали в values. Але як тільки ми отримуємо Promise, як елемент values, наше рішення перестає працювати.

Це тому, що ми не розгорнули Promise, а повернули його. Замінимо тип T на умовний тип, який буде перевіряти, чи є елемент Promise. Якщо елемент це Promise то повертаємо внутрішній тип, інакше – тип без змін.

declare function PromiseAll<T extends unknown[]>(
  values: readonly [...T],
): Promise<T extends Promise<infer R> ? R : T>;

Рішення досі помилкове, тому що T, не об’єднання, а кортеж. Тож, потрібно проітерувати всі елементи кортежу і перевірити, є поточний елемент Promise чи ні.

declare function PromiseAll<T extends unknown[]>(
  values: readonly [...T],
): Promise<{ [P in keyof T]: T[P] extends Promise<infer R> ? R : T[P] }>;

Посилання