Back to TypeScript tutorials
Intermediate13 min read

Async/Await & Promises

Type asynchronous code correctly with Promise generics, error unions, and concurrent operation typing.

Typing Promises

Promise<T> describes the resolved value type. Async functions implicitly return Promise<T> based on return expressions. Annotate async function return types on exported APIs for stable contracts.

Avoid `Promise<any>` — specify the resolved shape or use `unknown` and narrow after await. Generic Promise utilities preserve types through chains.

  • `Promise<T>` for resolved type
  • Annotate exported async returns
  • Never `Promise<any>` on public APIs
async function fetchUser(id: string): Promise<User | null> {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) return null;
  return res.json();
}

Error Handling Types

Model errors as discriminated unions or custom Error subclasses with typed properties. Libraries like neverthrow and ts-results provide Result types that make failure explicit in signatures.

Throwing remains idiomatic in many Node frameworks — type catch blocks with `unknown` and narrow with `instanceof` rather than assuming Error shape.

  • Result types for explicit errors
  • Catch `unknown`, narrow safely
  • Custom Error classes with typed fields
type Result<T> = { ok: true; value: T } | { ok: false; error: AppError };

Typing Promise Combinators

`Promise.all` on a tuple preserves tuple types in TypeScript — each resolved type maps to the corresponding input. `Promise.all` on a homogeneous array returns `T[]`. `Promise.allSettled` returns status objects typed per input.

Use `Awaited<ReturnType<typeof fn>>` to extract resolved types from async functions in utility types without duplicating definitions.

  • Tuple preservation in Promise.all
  • Awaited for resolved type extraction
  • allSettled for partial failure typing
const [user, posts] = await Promise.all([
  fetchUser(id),
  fetchPosts(id),
] as const);

Async Iteration Types

Async generators return `AsyncGenerator<T>`. `for await...of` works on `AsyncIterable<T>`. Type streaming parsers and paginated fetchers with these interfaces for clarity.

Node.js streams implement async iteration in modern typings — cast carefully or use wrapper utilities when stream types and async iterator types diverge across @types versions.

  • AsyncGenerator for streaming
  • AsyncIterable interface
  • Type paginated fetch pipelines

Framework Integration

Next.js Server Components and Route Handlers use async functions — return types must be serializable. React Server Actions type FormData inputs and return values explicitly for end-to-end safety.

Express and Fastify handlers benefit from typed request/response generics. Align async error middleware with your Result vs throw convention so types and runtime behavior match.

  • Server Components need serializable returns
  • Type Server Action inputs/outputs
  • Align throw vs Result with middleware

Get In Touch


Ready to discuss your next project? Drop me a message.