Типизация Promise.all над кортежем разнородных промисов
loadAll должна резолвиться в кортеж [User, number, boolean] — выведенный, а не аннотированный и не приведённый. Сейчас промисы сначала собираются в переменную-массив, и эта переменная расширяет их до одного массива с объединением внутри.
Ограничения: никаких as, никакого any, никакой явной аннотации типа возврата у loadAll. Кортеж должен получиться только за счёт вывода типов.
interface User { id: string }
declare const userP: Promise<User>;
declare const countP: Promise<number>;
declare const flagP: Promise<boolean>;
async function loadAll() {
const pending = [userP, countP, flagP];
// ваш код здесь
}
Допишите реализацию.
У Promise.all есть перегрузка для кортежа, которая прогоняет каждую позицию через Awaited<T[P]>, поэтому каждый слот сохраняет свой тип. Она срабатывает, только пока аргумент остаётся кортежем: в обычной const-переменной элементы расширяются до (Promise<User> | Promise<number> | Promise<boolean>)[], и результат схлопывается в массив объединения. Сохраните кортеж через as const или передайте литерал массива прямо в вызов.
- ✗Считать, что
const-массив промисов остаётся кортежем, а не расширяется до массива - ✗Хвататься за
as [User, number, boolean]вместо сохранения вывода кортежа - ✗Думать, что
Promise.allв принципе может дать только массив объединения
- →Как
Promise.allSettledменяет тип элементов получившегося кортежа? - →Почему литерал массива, переданный в
Promise.allнапрямую, уже выводится как кортеж?
Почему ломается вывод
const pending = [userP, countP, flagP] — это изменяемый массив, а не кортеж. TypeScript расширяет тип литерала до (Promise<User> | Promise<number> | Promise<boolean>)[]: длина забыта, позиции забыты. Перегрузка Promise.all для кортежа при таком аргументе не срабатывает, и результат становится (User | number | boolean)[].
Решение
Зафиксируйте кортеж через as const — это единственное, чего не хватает:
interface User { id: string }
declare const userP: Promise<User>;
declare const countP: Promise<number>;
declare const flagP: Promise<boolean>;
async function loadAll() {
const pending = [userP, countP, flagP] as const;
// ^? readonly [Promise<User>, Promise<number>, Promise<boolean>]
return Promise.all(pending);
// ^? Promise<[User, number, boolean]>
}
const [user, count, flag] = await loadAll();
// ^User ^number ^boolean
Сигнатура в lib.es2015.promise.d.ts — это отображаемый тип по позициям кортежа:
all<T extends readonly unknown[] | []>(
values: T,
): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;
{ -readonly [P in keyof T]: Awaited<T[P]> } идёт по индексам кортежа, снимает readonly и разворачивает каждую позицию через Awaited. Именно поэтому readonly-кортеж от as const подходит, а расширенный массив — нет.
Эквивалентный вариант — вообще не заводить переменную: литерал массива прямо в вызове контекстно типизируется параметром T и тоже выводится как кортеж.
async function loadAll() {
return Promise.all([userP, countP, flagP]); // тоже Promise<[User, number, boolean]>
}