TypeScriptMiddleTechnical
Что такое утилитный тип Awaited<T>?
Awaited<T> рекурсивно разворачивает Promise: Awaited<Promise<Promise<string>>> → string. Используется для вывода типа результата async-функций и совместим с union-типами.
Что такое Awaited<T>
Awaited<T> — встроенный утилитный тип TypeScript (добавлен в 4.5), который рекурсивно разворачивает вложенные Promise и объекты с методом then, имитируя то, что происходит при await в рантайме. До его появления разработчики писали громоздкие условные типы вручную.
Алгоритм разворачивания:
- Если
T—nullилиundefined, вернутьTкак есть. - Если
Tимеет методthen(onfulfilled)(то есть является thenable), взять тип первого аргументаonfulfilledи применитьAwaitedрекурсивно. - Иначе вернуть
Tбез изменений.
Базовые примеры
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number
type C = Awaited<boolean>; // boolean (не Promise — возвращается как есть)
type D = Awaited<Promise<string> | number>; // string | number (union раскрывается поэлементно)
Реальный пример: типизация ответа API
// api.ts
export async function fetchUser(id: string) {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json() as Promise<{ id: string; name: string; role: "admin" | "user" }>;
}
// Автоматически выводим тип без дублирования интерфейса
type User = Awaited<ReturnType<typeof fetchUser>>;
// { id: string; name: string; role: "admin" | "user" }
// Теперь можно типизировать кэш, хранилище, пропсы компонента
const cache = new Map<string, User>();
async function getUser(id: string): Promise<User> {
if (cache.has(id)) return cache.get(id)!;
const user = await fetchUser(id);
cache.set(id, user);
return user;
}
Пример с Promise.all и union
async function loadDashboard() {
return Promise.all([
fetch("/api/user").then(r => r.json() as Promise<{ name: string }>),
fetch("/api/stats").then(r => r.json() as Promise<{ count: number }>),
]);
}
// Тип результата Promise.all — кортеж, Awaited разворачивает каждый элемент
type DashboardData = Awaited<ReturnType<typeof loadDashboard>>;
// [{ name: string }, { count: number }]
// Используем в компоненте
function Dashboard({ data }: { data: DashboardData }) {
const [user, stats] = data;
return <div>{user.name}: {stats.count} jobs</div>;
}
Пример с обобщённым хелпером
// Универсальный retry-хелпер сохраняет тип через Awaited
async function withRetry<T>(
fn: () => Promise<T>,
attempts = 3
): Promise<T> {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (err) {
if (i === attempts - 1) throw err;
}
}
throw new Error("unreachable");
}
// TypeScript правильно выводит тип без явного указания
const user = await withRetry(() => fetchUser("123"));
// user: { id: string; name: string; role: "admin" | "user" }
Подводные камни
- Thenable ≠ Promise.
Awaitedраскрывает любой объект с методомthen, не только нативныйPromise. Если вы передаёте библиотечный объект-thenable (например, jQuery Deferred), результирующий тип может удивить. - Глубокая вложенность скрывает намерение.
Awaited<Promise<Promise<T>>>===T, но такой тип — признак архитектурной проблемы (функция возвращает Promise от Promise). Компилятор не выдаёт предупреждений. - Не работает как замена
inferвнутри условных типов. Если вы пишете свой условный тип вродеtype Unpack<T> = T extends Promise<infer U> ? U : T, он не рекурсивен. ИспользуйтеAwaitedвместо него. - Потеря
null/undefinedиз union.Awaited<Promise<string> | undefined>→string | undefined. Это корректно, но если ваш код предполагал проверку наundefinedдоawait, тип всё равно позволит её пропустить. - Не работает с функциями-генераторами.
AsyncGenerator— не thenable, его значенияAwaitedне раскрывает. Для генераторов используйтеAsyncGenerator<T>напрямую. - Путаница с
ReturnTypeсинхронных функций. Если функция неasync, но возвращаетPromiseявно,Awaited<ReturnType<typeof fn>>всё равно корректно раскроет тип. Проблемы нет, но разработчики иногда ошибочно думают, чтоAwaitedприменимо только кasync-функциям. - Версия TypeScript.
Awaitedдобавлен в 4.5. В монорепо со старыми пакетами, которые задают своиtsconfigс"target": "es5"и TS < 4.5, тип будет недоступен — это ломает сборку без очевидного сообщения об ошибке.
Common mistakes
- Смешивать «
Awaited<T>» с похожим механизмом без критерия выбора. - Игнорировать риск: неверно оценить границы применения темы «
Awaited<T>» и получить хрупкое решение. - Показывать только синтаксис и не объяснять поведение в runtime или сборке.
What the interviewer is testing
- Объясняет моделирование результата
awaitи рекурсивное разворачивание Promise. - Показывает на примере, как работает:
Awaitedкорректно обрабатывает вложенные Promise и union, поэтому полезен для типов async-функций и библиотечных helpers. - Называет production-нюанс и граничный случай для темы «
Awaited<T>».