BeCuriouzBeCuriouz

Learn something new every day

🔷 TypeScript
⏳

Typing Async Code: What await Unwraps

💡 async wraps your value in Promise<T>; await (and Awaited<T>) recursively unwraps it back to T.

An async function never returns a plain value — it always returns a Promise, where T is what the function eventually resolves to. TypeScript enforces this automatically: if the body returns a number, the function's type is Promise, even if you wrote : number by mistake (that's a type error). You annotate the resolved type and let the compiler wrap it:

ts
async function getPrice(): Promise<number> {
  return 42; // fine — TS wraps 42 in a Promise
}

The await keyword does the reverse: it unwraps a Promise back into a T. So const p = getPrice() has type Promise, but const n = await getPrice() has type number. A key subtlety is that await unwraps recursively — Promises don't nest. await on a Promise> still gives you a string, mirroring the runtime, where returning a promise from an async function auto-flattens it.

To name the unwrapped type without calling the function, use the built-in Awaited utility. Awaited> is number, and because it recurses, Awaited>> is boolean. This is exactly the type you get from await, which is handy when combining promises: await Promise.all([getPrice(), getName()]) infers a tuple [number, string].

Remember from earlier cards that union and inference rules still apply here — an async function that might return a string or number is typed Promise, and awaiting it gives string | number, which you'll often need to narrow before use.