Q17 of 38 · TypeScript
How do you correctly type async functions and Promise values in TypeScript?
Short answer
Short answer: An `async` function's return type is `Promise<T>`. Use `Awaited<T>` (TypeScript 4.5+) to unwrap the resolved type of a Promise generically. Annotate return types on public async functions for clarity; TypeScript infers them on private ones. Handle errors with `unknown` in catch blocks under strict mode.
Detail
Typing async code correctly prevents a class of runtime bugs where a resolved value is treated as its raw Promise.
Async function return type: Any function declared async implicitly returns Promise<T> where T is the type of the resolved value. If you annotate async function fetch(): User, TypeScript will silently wrap it in Promise<User>. To annotate explicitly: async function fetch(): Promise<User>.
AwaitedAwaited<Promise<string>> is string; Awaited<Promise<Promise<string>>> is also string (recursively unwrapped). Use it in generic helper types: type Result<F extends () => Promise<unknown>> = Awaited<ReturnType<F>>.
Promise in function signatures: Returning Promise<void> signals the caller must await but the resolved value is unused. Promise<never> means the function always rejects or never resolves.
Error typing in catch: Under strict mode (useUnknownInCatchVariables), err in catch is unknown. Always narrow: if (err instanceof Error) { ... }.
Typed Promise chains: .then() and .catch() callbacks are typed by the Promise's generic parameter — TypeScript infers the handler argument automatically.
// EXAMPLE
// Return type annotation on async function
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return res.json() as Promise<User>;
}
// Awaited — unwrap resolved type
type FetchResult = Awaited<ReturnType<typeof fetchUser>>; // User
// Promise<void> — caller should await but result unused
async function cleanup(): Promise<void> {
await db.disconnect();
}
// Generic async helper
async function retry<T>(
fn: () => Promise<T>,
times: number
): Promise<T> {
let lastErr: unknown;
for (let i = 0; i < times; i++) {
try { return await fn(); }
catch (e) { lastErr = e; }
}
throw lastErr;
}
// Catch with unknown
try {
await fetchUser(1);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
console.error(message);
}