Как типизировать функцию, работающую с массивом промисов с разными типами в TypeScript
Что проверяют
Умеете ли вы сохранить тип каждого элемента, а не свалить всё в объединение. Интервьюер ждёт вывода кортежа, сопоставленного типа и Awaited. Следом обычно спрашивают про Promise.allSettled или почему массив, объявленный заранее, ломает вывод.
Как отвечать
Наивная версия function all<T>(promises: Promise<T>[]): Promise<T[]> компилируется, но при смешанных типах T выводится как number | string | boolean, и результат становится массивом объединений. Что results[0] число, компилятор уже не знает.
Правильный каркас: принимать кортеж и отображать его поэлементно.
function all<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>
Разберите по частям:
| []в ограничении: подсказка компилятору выводить из литерала кортеж[Promise<number>, Promise<string>], а не массив. Похожий эффект даёт параметр видаvalues: [...T];- сопоставленный тип по
keyof T: на кортежах он сохраняет длину и позиции, и каждый элемент получает свой тип; Awaited<T[K]>: разворачивает промис, в том числе вложенный, и пропускает значения, которые промисами не являются;readonlyи-readonly: первое позволяет передать кортежas const, второе снимает модификатор с результата.
Ровно так объявлен встроенный Promise.all в стандартной библиотеке, поэтому часто хватает его самого. Своя типизация нужна, когда функция что-то добавляет: таймаут, повтор, обработку ошибок.
Для allSettled элемент имеет тип PromiseSettledResult<Awaited<T[K]>>, и перед чтением value его сужают проверкой status === "fulfilled". Тем же сопоставленным типом типизируется и объект промисов вроде { user: Promise<User>; posts: Promise<Post[]> }.
Где спотыкаются
Объявляют const promises = [p1, p2, p3] отдельной строкой и удивляются, что получили массив объединений. Литерал массива выводится как массив, нужен as const или передача прямо в вызов.
Пишут Promise<any[]> и считают задачу решённой. Все ошибки типов переезжают в рантайм.
Забывают, что Promise.all отклоняется на первой ошибке, и не вспоминают allSettled, когда нужны все результаты.