Skip to content
intermediate Phase 9 · TypeScript Async & Error Handling

Async TypeScript

Type async functions, Promises, and async iterables.

1h
0 problems
Topic Progress 0%

Typing Promises End to End

Typing Promises End to End

An async function always returns a Promise<T> — annotate the resolved value, never the wrapper:

async function genericGet<T>(url: string): Promise<T> {
  const res = await fetch(url);
  if (!res.ok) throw new HttpError(res.status);
  return (await res.json()) as T;
}

Combining promises stays typed without any:

const [user, perms] = await Promise.all([getUser(1), getPerms(1)]);
//    ^ User        ^ Perm[]   — tuple inference, not (User | Perm[])[]
const settled = await Promise.allSettled([a, b]);
// settled: PromiseSettledResult<...>[] with status "fulfilled" | "rejected"

Narrow with if (r.status === "fulfilled") r.value. Prefer PromiseLike<T> in signatures when you only need .then, so both native promises and thenables qualify.

Async Iterables and Generators

Async Iterables and Generators

Streams that produce over time use AsyncIterable<T>:

async function* paginate(perPage = 50): AsyncGenerator<Page> {
  let cursor: string | undefined;
  do {
    const page: Page = await fetchPage(cursor);
    yield page;
    cursor = page.nextCursor;
  } while (cursor);
}

for await (const page of paginate()) {
  process(page);
}

An async iterator’s next() returns Promise<IteratorResult<T>>, which is exactly why only for await (or manual .next() + await) can consume it — a plain for..of over an async iterable is a type error. Type custom containers by implementing [Symbol.asyncIterator](): AsyncIterableIterator<T>. Ensure "lib" includes ES2018.AsyncIterable (or target ≥ es2018); otherwise the globals are missing from lib.dom-less configs.

Pitfalls and Interview Angles

Pitfalls and Interview Angles

  • Floating promises: a returned Promise nobody awaits hides rejections and races ahead of your logic. typescript-eslint’s no-floating-promises turns that into a CI error; make fire-and-forget explicit with void doWork().
  • Sequential awaits in a loop serialize latency; for independent calls await Promise.all(tasks) — with a concurrency pool when the list is unbounded.
  • catch is unknown under useUnknownInCatchVariables (on via strict since TS 4.4): narrow with instanceof Error before reading .message.
  • Promise<void> vs void: type async framework handlers correctly so lint understands intent.

Interview angle: type a withConcurrency<T>(limit, tasks: (() => Promise<T>)[]): Promise<T[]> helper — generic return array, no any — and how PromiseSettledResult narrowing keeps failures exhaustive.

Quiz

1. What does an async function’s signature promise about its return type?

Question 1 options

2. Why does for await work on paginate() but plain for..of does not?

Question 2 options

3. What does no-floating-promises catch?

Question 3 options

Flashcards

Question

Promise<T> annotation rule

Answer

Write the resolved type: async f(): Promise<T> — TS adds the wrapper itself.

Question

Promise.all tuple typing

Answer

Awaiting an array literal of promises infers a fixed tuple of resolved types.

Question

AsyncGenerator<T>

Answer

Return type of async function*; consumed with for await, produces T over time.

Question

useUnknownInCatchVariables

Answer

strict-era default: catch (e) gives unknown — narrow before use.

Revision Notes

Key Takeaways

  • 1. Annotate resolved types; Promise.all gives tuples and allSettled gives status-union results
  • 2. Async iterables need for await — model streams with async generators
  • 3. Lint for floating promises and narrow unknown in catch blocks

Interview Tips

  • • Show the generic fetch wrapper and explain why the cast is confined to one boundary
  • • Describe concurrency control when asked to "await a list of tasks" — not every task at once

Cheat Sheet

async f(): Promise; Promise.all → tuple types; allSettled → {status,value|reason}; for await over AsyncGenerator; lint no-floating-promises; catch unknown.

Continue Learning