TypeScriptAwaited

Awaited Utility Type

The Awaited<T> utility type, introduced in TypeScript 4.5, recursively unwraps the type of a Promise. It models what await does at runtime — stripping away Promise wrappers layer by layer until a concrete value type is reached.

Before Awaited, extracting the resolved type of a promise required manual conditional types or third-party helpers. Now TypeScript ships it out of the box.

Why Awaited Exists

Consider a utility that takes any promise-returning function and wraps its return type. Prior to TypeScript 4.5 you had to write something like this:

TS
// Before TypeScript 4.5 — manual unwrapping
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type A = UnwrapPromise<Promise<string>>;          // string
type B = UnwrapPromise<Promise<Promise<number>>>; // Promise<number> — not fully unwrapped!
      

The one-level infer approach fails for nested promises. Awaited<T> fixes this by recursing until no more Promise layers exist.

Basic Syntax

TS
// Awaited is a built-in global — no import needed in TypeScript 4.5+

type A = Awaited<Promise<string>>;                    // string
type B = Awaited<Promise<Promise<number>>>;           // number
type C = Awaited<Promise<Promise<Promise<boolean>>>>; // boolean
type D = Awaited<string>;                             // string (non-promise passthrough)
type E = Awaited<number | Promise<string>>;           // number | string
      
Note
Awaited is a built-in global utility type since TypeScript 4.5. You do not need to import it — it is available everywhere, just like Partial or ReturnType.
How Awaited Is Defined Internally

The TypeScript standard library defines Awaited using recursive conditional types that target the .then method shape rather than Promise directly:

TS
// Conceptual definition (from lib.es5.d.ts in the TypeScript compiler)
type Awaited<T> =
  T extends null | undefined
    ? T
    : T extends object & { then(onfulfilled: infer F, ...args: infer _): any }
      ? F extends (value: infer V, ...args: infer _) => any
        ? Awaited<V>   // recurse on the resolved value
        : never
      : T;             // not thenable — return as-is
      

Key points:

  • It checks for a .then method rather than instanceof Promise, so it handles any thenable (jQuery Deferred, custom promise-likes, etc.).
  • It recurses on the resolved value V, handling arbitrarily deep nesting.
  • null and undefined short-circuit early to prevent infinite recursion.
Combining Awaited with ReturnType

The single most common use of Awaited is paired with ReturnType to derive the resolved value type of an async function — the holy grail for API typing:

TS
async function fetchUser(id: number) {
  const response = await fetch(`/api/users/${id}`);
  return response.json() as Promise<{ id: number; name: string; email: string }>;
}

// ReturnType gives you the Promise wrapper
type FetchUserReturn = ReturnType<typeof fetchUser>;
// => Promise<{ id: number; name: string; email: string }>

// Awaited unwraps it to the concrete value
type User = Awaited<ReturnType<typeof fetchUser>>;
// => { id: number; name: string; email: string }

// Use it to type variables, function parameters, and component props
function UserCard(props: { user: User }) {
  return `${props.user.name} (${props.user.email})`;
}
      
Tip
The combination Awaited<ReturnType<typeof fn>> is one of the most useful patterns in TypeScript. Derive your types from the implementation — never duplicate them manually.
Awaited with Promise.all

When you run multiple promises in parallel with Promise.all, you sometimes need to type the resulting tuple explicitly. Awaited makes this clean:

TS
const fetchName = async () => 'Alice';
const fetchAge  = async () => 30;
const fetchTags = async () => ['ts', 'react'] as string[];

// TypeScript infers this correctly already from Promise.all overloads
const [name, age, tags] = await Promise.all([fetchName(), fetchAge(), fetchTags()]);
// name: string, age: number, tags: string[]

// A reusable helper that unwraps a tuple of Promises:
type AwaitAll<T extends readonly unknown[]> = {
  [K in keyof T]: Awaited<T[K]>
};

type Results = AwaitAll<[Promise<string>, Promise<number>, Promise<boolean>]>;
// => [string, number, boolean]

// Useful when building typed wrappers around Promise.all
function runAll<T extends readonly Promise<unknown>[]>(
  promises: T,
): Promise<AwaitAll<T>> {
  return Promise.all(promises) as any;
}
      
Generic Cache with Awaited

TS
type AsyncFn = (...args: any[]) => Promise<any>;

type CacheEntry<F extends AsyncFn> = {
  value: Awaited<ReturnType<F>>;
  fetchedAt: Date;
};

async function loadConfig() {
  return { theme: 'dark' as const, locale: 'en', version: 42 };
}

async function loadUser(id: string) {
  return { id, name: 'Alice', roles: ['admin', 'user'] };
}

// TypeScript infers the stored value type precisely
type ConfigCache = CacheEntry<typeof loadConfig>;
// => { value: { theme: 'dark'; locale: string; version: number }; fetchedAt: Date }

type UserCache = CacheEntry<typeof loadUser>;
// => { value: { id: string; name: string; roles: string[] }; fetchedAt: Date }

// A concrete implementation
class AsyncCache<F extends AsyncFn> {
  private entry: CacheEntry<F> | null = null;

  async get(fn: F, ...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> {
    if (!this.entry) {
      this.entry = { value: await fn(...args), fetchedAt: new Date() };
    }
    return this.entry.value;
  }
}
      
Awaited with Thenables

Because Awaited looks for a .then shape, it unwraps custom thenables — not just native Promise:

TS
interface MyThenable<T> {
  then<TResult>(
    onfulfilled: (value: T) => TResult,
    onrejected?: (reason: unknown) => TResult,
  ): MyThenable<TResult>;
}

type UnwrappedSingle = Awaited<MyThenable<string>>;
// => string

type UnwrappedNested = Awaited<MyThenable<MyThenable<number>>>;
// => number

// Works with bluebird, zen-observable, and other thenable libraries
      
Practical API Layer Pattern

TS
// api.ts — all your fetchers in one place
export const api = {
  getUser:    async (id: string)     => ({ id, name: 'Alice', createdAt: new Date() }),
  listPosts:  async (userId: string) => [{ id: '1', title: 'Hello TS' }],
  deletePost: async (postId: string) => ({ success: true as const }),
} as const;

// types.ts — derive types automatically (single source of truth)
type Api = typeof api;

export type User        = Awaited<ReturnType<Api['getUser']>>;
export type Post        = Awaited<ReturnType<Api['listPosts']>>[number];
export type DeleteResult = Awaited<ReturnType<Api['deletePost']>>;

// components/UserCard.tsx
function displayUser(user: User) {
  // user.id: string, user.name: string, user.createdAt: Date — all inferred
  console.log(`${user.name} joined ${user.createdAt.toDateString()}`);
}
      
Success
Deriving types from the implementation means your types update automatically when the function changes. You never have a stale type definition again.
Memoize Any Async Function — Type-Safe

TS
function memoizeAsync<F extends (...args: any[]) => Promise<any>>(fn: F) {
  const cache = new Map<string, Awaited<ReturnType<F>>>();

  return async (...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> => {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key)!;

    const result: Awaited<ReturnType<F>> = await fn(...args);
    cache.set(key, result);
    return result;
  };
}

async function computeSquare(n: number): Promise<number> {
  return n * n;
}

const memoSquare = memoizeAsync(computeSquare);
// inferred as: (n: number) => Promise<number>  ✓

const result = await memoSquare(7); // number ✓
      
Common Mistakes

TS
// ❌ Mistake 1: Applying Awaited to a function type instead of its return type
async function getData() { return { count: 42 }; }

type Wrong   = Awaited<typeof getData>;            // typeof getData — unchanged!
type Correct = Awaited<ReturnType<typeof getData>>; // { count: number } ✓

// ❌ Mistake 2: Storing an async result without awaiting or unwrapping the type
const raw: ReturnType<typeof getData> = getData();   // Promise<{count:number}> — not the value
const val: Awaited<ReturnType<typeof getData>> = await getData(); // {count:number} ✓

// ❌ Mistake 3: Expecting Awaited to resolve rejected promises
// Awaited is a compile-time type tool — it has no effect on runtime rejection handling
      
Warning
Awaited<typeof myAsyncFn> does NOT unwrap the return value — it returns the function type unchanged because a function is not a thenable. Always apply ReturnType first.
Awaited vs Manual Conditional Types

Approach

Handles Nesting

Handles Thenables

Built-in

T extends Promise<infer U> ? U : T

No (one level)

No

No

Recursive conditional type

Yes

No

No

Awaited<T>

Yes

Yes

Yes (TS 4.5+)

Summary
  • Awaited<T> recursively unwraps Promise and thenable layers to the resolved value type.

  • It is a built-in global utility type since TypeScript 4.5 — no import needed.

  • Combine it with ReturnType to derive types from async function implementations.

  • It works on any thenable object, not just native Promises.

  • Use it in generic constraints to enforce resolved types through higher-order functions.

  • The pattern Awaited<ReturnType<typeof fn>> is idiomatic — derive, don't duplicate.