Awaited<T> and unwrapping nested promise types
Published April 6, 2027
type Nested = Promise<Promise<Promise<string>>>;
type Flat = Awaited<Nested>;
export async function loadUser(): Promise<{ id: string; name: string }> {
return { id: "1", name: "Ada" };
}
export type LoadedUser = Awaited<ReturnType<typeof loadUser>>;
export const greet = (user: LoadedUser): string => `Hello, ${user.name}`;
export const flatValue: Flat = "done";What
Awaited<T> is a built-in TypeScript utility type that unwraps however many layers of Promise (or any thenable) surround a type, down to the value that's actually left once every layer resolves — mirroring what await does to a value at runtime.
Why it matters
Before Awaited shipped (TypeScript 4.5, driven by Promise.all's type getting more accurate), unwrapping a promise type meant a hand-rolled conditional type, and it was easy to get subtly wrong for the nested case: Promise<Promise<T>> is a real type you can end up with — for example, an async function that returns another async function's still-pending promise — and a naive T extends Promise<infer U> ? U : T only strips one layer, leaving Promise<T> instead of T. Awaited recurses until there's nothing left to unwrap, matching the actual runtime behavior of await, which also keeps awaiting until it gets a non-thenable value.
How it works
Flat, fromAwaited<Nested>whereNestedisPromise<Promise<Promise<string>>>, resolves straight tostring— all three layers are gone, not just one.LoadedUsercomposesAwaitedwithReturnType:ReturnType<typeof loadUser>gets theasyncfunction's declared return type,Promise<{ id: string; name: string }>, andAwaitedunwraps that down to the plain object. This combination is the actual common case — you usually want "what this async function resolves to," not "what promise type it returns."greettakesLoadedUserdirectly, as ifloadUserhad never beenasyncin the first place, which is the point:Awaited<ReturnType<...>>is what lets calling code work in resolved types when the promise layer is just plumbing.
Gotchas
Awaitedunwraps thenables generally, not onlyPromise. Any object with a.thenmethod participates in the same unwrapping, matching howawaittreats them at runtime too.ReturnType<typeof loadUser>alone, withoutAwaited, isPromise<{ id: string; name: string }>— a very common half-step that type-checks but is rarely what you actually want to store or pass around.
Related
- Conditional types +
infer—Awaitedis implemented with exactly this mechanism; theAsyncReturnTypepattern is a simplified, one-level-deep version of whatAwaiteddoes generally. Promise.all's return type — the motivating use case forAwaited's addition, unwrapping a tuple of promises into a tuple of their resolved values.