⚡IntermediateAsync & Error Handling
Async TypeScript
Type async functions, Promise chains, Promise.all, async generators, and handle async error types correctly in TypeScript.
Overview
TypeScript has full support for async/await and Promises. The key insight is that an `async` function always returns `Promise<T>` where T is the type of the value you `return`. TypeScript infers this automatically.
**Promise combinators** like `Promise.all`, `Promise.allSettled`, `Promise.race`, and `Promise.any` each have precise TypeScript types that preserve the types of individual promises - `Promise.all([p1, p2])` where p1 is `Promise<string>` and p2 is `Promise<number>` returns `Promise<[string, number]>`.
**Async generators** (`async function*`) return `AsyncGenerator<T>` and let you `yield` values over time, perfect for streaming APIs. TypeScript types each `yield` value and the final `return` value separately.
One important caveat: TypeScript cannot type the `catch` clause error variable as anything other than `unknown` in strict mode - because any type of value can be thrown. You must narrow it before use.
Key Points
- •Async function return type: `async function f(): Promise<string>` - or let TypeScript infer it
- •`Promise<T>` is the type of a pending value that resolves to T
- •`Awaited<T>` is the utility type that unwraps a Promise: `Awaited<Promise<string>>` = `string`
- •`Promise.all([p1, p2])` infers a tuple: `Promise<[T1, T2]>` - preserves individual types
- •`Promise.allSettled` returns `PromiseSettledResult<T>[]` - each is either fulfilled or rejected
- •Catch clause: `catch (e)` - `e` is typed as `unknown`; must narrow with `instanceof Error`
- •Async generator: `async function*` returns `AsyncGenerator<YieldType, ReturnType, NextType>`
Examples
Typed async functions and inference
interface User {
id: number;
name: string;
email: string;
}
// TypeScript infers return type as Promise<User>
async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json(); // Promise<any> - we trust the API contract
}
// Chaining with typed steps
async function getUserName(id: number): Promise<string> {
const user = await fetchUser(id); // User
return user.name; // string - TypeScript tracks this
}
// Async IIFE (immediately invoked)
(async () => {
try {
const name = await getUserName(1);
console.log(name); // string
} catch (error) {
// error is 'unknown' in strict mode - must narrow
if (error instanceof Error) {
console.error(error.message);
}
}
})();Promise combinators with precise types
async function fetchString(): Promise<string> { return 'hello'; }
async function fetchNumber(): Promise<number> { return 42; }
async function fetchBool(): Promise<boolean> { return true; }
// Promise.all preserves tuple types
const [s, n, b] = await Promise.all([
fetchString(), // Promise<string>
fetchNumber(), // Promise<number>
fetchBool(), // Promise<boolean>
]);
// s: string, n: number, b: boolean
// Promise.allSettled - each result is discriminated union
const results = await Promise.allSettled([fetchString(), fetchNumber()]);
for (const result of results) {
if (result.status === 'fulfilled') {
console.log('Value:', result.value); // string | number
} else {
console.log('Error:', result.reason); // unknown
}
}
// Promise.race - returns the type of the first to settle
const first = await Promise.race([fetchString(), fetchNumber()]);
// first: string | number
// Promise.any - same as race but ignores rejections
const winner = await Promise.any([fetchString(), fetchNumber()]);
// winner: string | numberAsync generators for streaming data
// AsyncGenerator<YieldType, ReturnType, NextType>
async function* paginate<T>(
fetchPage: (page: number) => Promise<T[]>,
maxPages = 10
): AsyncGenerator<T, void, unknown> {
for (let page = 1; page <= maxPages; page++) {
const items = await fetchPage(page);
if (items.length === 0) return; // done
for (const item of items) {
yield item; // TypeScript knows this is T
}
}
}
// Simulated API
async function fetchUsersPage(page: number): Promise<User[]> {
if (page > 3) return [];
return [
{ id: page * 10 + 1, name: `User${page}A`, email: '' },
{ id: page * 10 + 2, name: `User${page}B`, email: '' },
];
}
// Consume with for-await-of
for await (const user of paginate(fetchUsersPage)) {
console.log(user.name); // User is fully typed
}
interface User { id: number; name: string; email: string }Common Mistakes
- Using `Promise<any>` as a return type - always type the resolved value; `Promise<User>` is far safer than `Promise<any>`
- Not handling the `unknown` type in catch clauses: `catch (e) { e.message }` errors in strict mode; use `if (e instanceof Error)` first
- Floating promises - calling an async function without `await` or `.catch()` silently drops errors; TypeScript with `"noFloatingPromises"` in eslint or `"noUncheckedSideEffectImports"` can catch this
Note
Use `Awaited<ReturnType<typeof myAsyncFunction>>` to extract the resolved type of any async function without repeating the type. It works with nested promises too: `Awaited<Promise<Promise<string>>>` = `string`.
Exercises
Write TypeScript code to solve each exercise. Click Run to compile and execute it right here, then mark as done.
Loading editor…
Completed: 0 / 3 exercises
Knowledge Check
Question 1 of 4