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
Promisenobody awaits hides rejections and races ahead of your logic.typescript-eslint’sno-floating-promisesturns that into a CI error; make fire-and-forget explicit withvoid doWork(). - Sequential awaits in a loop serialize latency; for independent calls
await Promise.all(tasks)— with a concurrency pool when the list is unbounded. catchisunknownunderuseUnknownInCatchVariables(on viastrictsince TS 4.4): narrow withinstanceof Errorbefore reading.message.Promise<void>vsvoid: 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?
2. Why does for await work on paginate() but plain for..of does not?
3. What does no-floating-promises catch?
Flashcards
Question
Promise<T> annotation rule
Click to reveal answer
Answer
Write the resolved type: async f(): Promise<T> — TS adds the wrapper itself.
Question
Promise.all tuple typing
Click to reveal answer
Answer
Awaiting an array literal of promises infers a fixed tuple of resolved types.
Question
AsyncGenerator<T>
Click to reveal answer
Answer
Return type of async function*; consumed with for await, produces T over time.
Question
useUnknownInCatchVariables
Click to reveal answer
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
Continue Learning
Before You Start
- No prerequisites listed.