Typing Promises & async/await

Promise<T> is TypeScript's type for an asynchronous value that arrives later; await unwraps it to a T , every async function returns a Promise, and Awaited<T> peels those Promise layers off in the type system.

Learn Typing Promises & async/await in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.

Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

A Promise<T> is like a coat-check ticket. You hand over your coat and get a ticket (the promise) immediately — but the ticket isn't the coat. await is redeeming the ticket later to get the actual coat (the T ) back. An async function always hands you a ticket, never the coat directly, which is why forgetting to await leaves you holding a ticket where you expected a value.

1. Promise<T> and Unwrapping with await

Any function that resolves a value later is typed to return a Promise<T> , where T is the value it eventually produces. Inside an async function, await pauses until the promise settles and hands you the unwrapped T — so await fetchUser(7) turns a into a User .

2. Async Return Types & Awaited<T>

Marking a function async means its return type is always wrapped in a Promise: return 42 gives . To go the other way in the type system — from a Promise type to the value it resolves to — use the built-in Awaited<T> , which even flattens nested promises.

3. A Typed Fetch Wrapper & Promise.all Tuples

A generic wrapper lets the call site say what shape it expects — resolves to a User . And when you await a fixed tuple via Promise.all , TypeScript keeps each position's type, so destructuring gives you correctly typed variables instead of a widened union.

🎯 Your Turn

Unwrap a promise from a tiny fetch-style function. Fill in the two blanks marked ___ , then run it.

No blanks this time — just a brief and a starting outline. Build the Promise.all pipeline yourself, run it, and check your output against the example in the comments.

Practice quiz

What does Promise<User> represent?

  • A User that exists right now
  • An array of Users
  • An async operation that will resolve to a User later
  • A User that may be null

Answer: An async operation that will resolve to a User later. Promise<T> is the type of an async operation that will eventually resolve to a value of type T - here, a User.

Inside an async function, what does await fetchUser(7) give you when fetchUser returns Promise<User>?

  • User
  • Promise<User>
  • Promise<Promise<User>>
  • unknown

Answer: User. await unwraps a Promise<T> to its inner value T, so awaiting a Promise<User> yields a User.

An async function does return 42. What is its return type?

  • number
  • 42
  • Promise<void>
  • Promise<number>

Answer: Promise<number>. Every async function wraps its return value in a Promise, so returning a plain number gives the type Promise<number>.

What does the built-in Awaited<T> utility type do?

  • Wraps T in a Promise
  • Recursively unwraps Promise layers to the resolved value type
  • Makes T optional
  • Converts T to an array

Answer: Recursively unwraps Promise layers to the resolved value type. Awaited<T> peels off Promise layers (even nested ones) to give the final resolved value type, mirroring what await does.

What is Awaited<Promise<Promise<string>>>?

  • string
  • Promise<string>
  • Promise<Promise<string>>
  • unknown

Answer: string. Awaited fully flattens nested promises, so Awaited<Promise<Promise<string>>> resolves to string.

What is Awaited<number> (a non-promise type)?

  • Promise<number>
  • never
  • number
  • void

Answer: number. Awaited is a no-op on non-promise types, so Awaited<number> is just number.

What type does await Promise.all([Promise.resolve("a"), Promise.resolve(1)]) produce?

Promise.all over a fixed tuple resolves to a tuple keeping each position's type: [string, number].

Why does reading user.name on const user = fetchUser(1) (no await) fail to type-check?

  • fetchUser doesn't exist
  • name is private
  • fetch returns void
  • user is a Promise<User>, not a User

Answer: user is a Promise<User>, not a User. Without await, user is a Promise<User>, which has no .name property - a very common bug. Add await first.

Why can't you use await directly in a non-async function?

  • await only works at the top level
  • await is only allowed inside async functions (or top-level module await)
  • await requires a try/catch
  • await needs a Promise.all

Answer: await is only allowed inside async functions (or top-level module await). 'await' expressions are only allowed within async functions (or as top-level await in a module). Mark the function async.

How do you grab the resolved result type of an async function fn for reuse?

  • ReturnType<typeof fn>
  • Promise<typeof fn>
  • Awaited<ReturnType<typeof fn>>
  • typeof fn.return

Answer: Awaited<ReturnType<typeof fn>>. ReturnType<typeof fn> is the Promise; wrapping it in Awaited<...> unwraps it to the resolved value type.