Type Promise Return Values
Learn to type what a promise resolves to in TypeScript. Covers explicit Promise<T> annotations, inference from async functions, ReturnType, and Awaited for extracting resolved types.
A promise return value is what the promise eventually resolves to. In TypeScript, you type this value by declaring Promise<T>, where T is the resolved type. You can annotate it explicitly, let inference figure it out, or derive it from other types using utility types like ReturnType and Awaited.
This article covers all three approaches and when to use each one.
Explicit Promise Annotations
The most direct way to type a promise return value is to annotate the function's return type with the resolved type wrapped in Promise.
async function fetchUserName(id: number): Promise<string> {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user.name;
}The annotation Promise of string tells TypeScript and anyone reading the code that this function eventually produces a string. If you later change the implementation to return a number, TypeScript catches the mismatch at compile time.
Explicit annotations are most valuable on exported functions and public API surfaces. They serve as a contract: callers know exactly what type to expect, and the implementation is held to that contract.
Letting TypeScript Infer the Type
For internal functions and simple cases, you can skip the annotation. TypeScript infers the return type from the actual return statements in the function body.
async function fetchCount() {
return response.headers.get("X-Total-Count");
}The inferred return type is Promise of (string or null), because the headers get method can return null. Hovering over the function name in your editor shows the inferred type. Since it is derived from the actual code, it always matches reality.
Inference also works across multiple code paths. TypeScript computes the union of all possible return types from every branch. If one branch returns "ok" and another returns 404, the inferred return type becomes Promise of (string or number).
Using ReturnType to Extract Return Types
TypeScript provides the ReturnType utility type to extract the return type from any function signature. This is useful when you want to reference the return type without duplicating it.
async function createUser(name: string, email: string) {
return { id: 1, name, email };
}
type CreateUserResult = ReturnType<typeof createUser>;ReturnType gives you the full return type, which for an async function is a Promise wrapping the object type. You get Promise of the user object, not just the object itself. To get just the resolved value type, you need one more step.
Using Awaited to Get the Resolved Value
Awaited recursively unwraps promises to find the innermost resolved type. Combine it with ReturnType to extract the resolved value from an async function.
async function getDashboardData() {
const [user, stats] = await Promise.all([
fetchCurrentUser(),
fetchSiteStats(),
]);
return { user, stats };
}
type DashboardData = Awaited<ReturnType<typeof getDashboardData>>;ReturnType produces Promise of the dashboard object, and Awaited unwraps the Promise layer to give just the object type. You now have a reusable type for the dashboard data without duplicating the shape.
The two-step pipeline extracts the resolved type from any async function. You can reuse the resulting type for variable declarations, component props, generic parameters, or other return type annotations.
Typing Variables That Hold Promises
When you store a promise in a variable before awaiting it, TypeScript infers the variable type from the initializer. If you need to declare a variable without an initializer, you must annotate it with the full Promise type.
let deferredPromise: Promise<string>;
if (condition) {
deferredPromise = Promise.resolve("branch A");
} else {
deferredPromise = Promise.resolve("branch B");
}The annotation Promise of string constrains the variable so that only promises resolving to strings can be assigned to it. Both branches satisfy this constraint, so the code compiles.
Typing Callbacks That Return Promises
When a callback returns a promise, TypeScript infers the full Promise type. This matters when the callback's return value flows into another generic type.
function withRetry<T>(fn: () => Promise<T>): Promise<T> {
return fn().catch(() => fn());
}
const result = withRetry(() => fetchUser(1));The generic parameter T captures the resolved type of the callback's promise. When you pass a callback that returns Promise of User, T is inferred as User, and the entire chain stays typed. The caller gets back a Promise of User without writing any type annotations.
Common Mistakes
Using raw ReturnType without Awaited. ReturnType of an async function is a Promise, not the resolved value. If you assign it to a variable expecting the resolved value, the types will not match. Always wrap with Awaited when you need the inner type.
Not annotating exported async functions. Inference works, but explicit return types on public APIs prevent accidental type drift. A small change to an internal implementation should not silently change the return type that consumers depend on. For more on how async functions interact with the type system, see async and await in TypeScript.
Forgetting that promises can be nested. Promise of Promise of number is a valid type. Awaited handles this, but manual unwrapping with ReturnType alone does not.
For practical examples of wrapping fetch calls, see build a typed fetch wrapper.
Rune AI
Key Insights
- Annotate async function return types as Promise of T for public-facing functions.
- TypeScript infers the resolved type from the return statement inside async functions.
- Use ReturnType to extract the full return type from any function.
- Wrap with Awaited to unwrap the promise and get the resolved value type.
- Inconsistent return types across branches produce compile errors.
Frequently Asked Questions
When should I explicitly annotate a promise return type?
How do I get the resolved type from a function that returns a promise?
Does TypeScript warn me if I return the wrong type from an async function?
Conclusion
Typing promise return values in TypeScript comes down to three patterns: explicit Promise annotations for public APIs, letting inference handle internal code, and using ReturnType with Awaited to derive resolved types from existing functions. Each pattern has its place. Use explicit annotations where stability matters, inference where brevity helps, and utility types where you need to extract or transform resolved types.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.