TypeScript Async and Fetch Types
TypeScript async functions always return a Promise<T>, where T is the type of the value you return. Typing promises and API responses correctly is one of the most useful things TypeScript does in real applications.
Promise Return Types
Inside then or after await, TypeScript knows the value is a number.
const wait = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));
async function getScore(): Promise<number> {
await wait(100);
return 87;
}
getScore().then((s) => console.log("Score:", s + 3));Score: 90
Typing a Fetch Response
response.json() returns Promise<any>, so TypeScript cannot check it. Give the result a type in a small helper, ideally with runtime validation for untrusted APIs.
type Todo = { id: number; title: string; completed: boolean };
async function getJson<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return (await res.json()) as T;
}
const todo = await getJson<Todo>("https://jsonplaceholder.typicode.com/todos/1");
console.log(todo.id, todo.completed);1 false
Errors Are unknown
In strict mode, the catch variable has type unknown because anything can be thrown. Narrow it before reading message.
async function safeLoad() {
try {
throw new Error("Timeout after 5s");
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
console.log("Failed:", msg);
}
}
safeLoad();Failed: Timeout after 5s
A Typed Result Pattern
Returning a discriminated union instead of throwing makes callers handle both outcomes explicitly.
type Result<T> = { ok: true; value: T } | { ok: false; error: string };
async function parseAge(input: string): Promise<Result<number>> {
const n = Number(input);
return Number.isInteger(n) && n > 0 ? { ok: true, value: n } : { ok: false, error: "Invalid age" };
}
for (const input of ["29", "abc"]) {
const r = await parseAge(input);
console.log(r.ok ? `Age ${r.value}` : r.error);
}Age 29 Invalid age
Parallel Requests
Promise.all keeps the type of each position, returning a typed tuple.
const [count, label] = await Promise.all([
Promise.resolve(42),
Promise.resolve("items"),
]); // count: number, label: string
console.log(count, label.toUpperCase());42 ITEMS
Frequently asked questions
How do I type an async function in TypeScript?
Annotate the return type as Promise<T>, for example async function load(): Promise<User[]>. TypeScript can also infer it.
Why is the error in catch of type unknown?
JavaScript can throw any value, not just Error objects. With useUnknownInCatchVariables (part of strict), TypeScript makes you check the type first.
How do I type the JSON returned by fetch?
Assign it to a declared type or use a generic helper. For data you do not control, validate it at runtime with a schema library such as Zod.