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:
// 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
// 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
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:
// 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
.thenmethod rather thaninstanceof Promise, so it handles any thenable (jQuery Deferred, custom promise-likes, etc.). - It recurses on the resolved value
V, handling arbitrarily deep nesting. nullandundefinedshort-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:
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})`;
}
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:
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
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:
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
// 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()}`);
}
Memoize Any Async Function — Type-Safe
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
// ❌ 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
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.