Awaited
Awaited<T> models the type you get when you await a value or chain .then() on it. It recursively unwraps Promise types — even nested promises — to the underlying resolved value, mirroring how JavaScript’s runtime flattens thenables. Introduced in TypeScript 4.5, it underpins the return type of Promise.all and is the correct tool for typing the result of any async operation. This page shows how it unwraps single and nested promises, combines with ReturnType, and how it handles non-promise inputs.
Unwrapping a promise
At its simplest, Awaited<Promise<T>> gives back T. If the input is not a promise, it passes through unchanged.
type A = Awaited<Promise<string>>; // string
type B = Awaited<number>; // number (not a promise, unchanged)
type C = Awaited<Promise<User>>; // User
interface User {
id: number;
name: string;
}
This makes Awaited safe to apply to any type: it only strips promise layers and leaves everything else alone, so you can use it generically without first checking whether T is a promise.
Recursive unwrapping
Unlike a naive single-level unwrap, Awaited keeps unwrapping nested promises until it reaches a non-promise value, matching how await actually behaves at runtime.
type Nested = Awaited<Promise<Promise<Promise<boolean>>>>;
// boolean — fully flattened
// A hand-rolled single-level unwrap would stop early:
type Naive<T> = T extends Promise<infer U> ? U : T;
type Wrong = Naive<Promise<Promise<boolean>>>; // Promise<boolean> ❌
The standard Awaited recurses, so deeply wrapped promises collapse to their innermost type — something a one-shot infer cannot do on its own.
Combining with ReturnType
The most common real-world use is unwrapping the result of an async function. ReturnType gives Promise<X>; Awaited turns it into X.
async function fetchOrders() {
return [{ id: 1 }, { id: 2 }];
}
type OrdersPromise = ReturnType<typeof fetchOrders>; // Promise<{ id: number }[]>
type Orders = Awaited<ReturnType<typeof fetchOrders>>; // { id: number }[]
function process(orders: Awaited<ReturnType<typeof fetchOrders>>) {
orders.forEach((o) => console.log(o.id));
}
This pattern keeps a consumer’s parameter type tied to whatever the async function resolves to — see Parameters & ReturnType.
How it is implemented
Awaited is a recursive conditional type that inspects the .then method of a thenable, not just Promise. A simplified version captures the idea:
type MyAwaited<T> =
T extends null | undefined ? T :
T extends { then(onfulfilled: (value: infer V) => any): any } ? MyAwaited<V> :
T;
It checks whether T is a thenable by inferring the value passed to then’s callback (V), then recurses on V. Non-thenables and nullish values are returned as-is. The real definition adds extra checks but follows this structure.
Behavior summary
| Input | Awaited<Input> |
|---|---|
Promise<string> | string |
Promise<Promise<number>> | number |
string | string |
Promise<T> | T | T |
| custom thenable | its resolved value |
Prefer
Awaited<ReturnType<typeof fn>>over manually unwrapping withT extends Promise<infer U>. The built-in handles nested promises, unions, and thenables that your one-off conditional will miss.
Best Practices
- Use
Awaited<T>instead of a hand-writtenT extends Promise<infer U>to handle nesting correctly. - Pair it with
ReturnTypeto type the resolved result of async functions in one expression. - Rely on its pass-through behavior: applying it to a non-promise type is safe and a no-op.
- Reach for it when typing
Promise.allresults or aggregating multiple async sources. - Remember it works on any thenable, not only native
Promiseobjects. - Avoid double-wrapping (
Promise<Awaited<T>>) unless you specifically need to re-promisify a value.