Skip to content
TypeScript ts utility-types 3 min read

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

InputAwaited<Input>
Promise<string>string
Promise<Promise<number>>number
stringstring
Promise<T> | TT
custom thenableits resolved value

Prefer Awaited<ReturnType<typeof fn>> over manually unwrapping with T 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-written T extends Promise<infer U> to handle nesting correctly.
  • Pair it with ReturnType to 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.all results or aggregating multiple async sources.
  • Remember it works on any thenable, not only native Promise objects.
  • Avoid double-wrapping (Promise<Awaited<T>>) unless you specifically need to re-promisify a value.
Last updated June 29, 2026
Was this helpful?