Lesson 8 of 13
Async TypeScript
Learn how are typed: they return `<T>`, and `await` gives you the `T`. You will also see how to handle safely, since a caught error has the type `unknown`.
// async functions always return Promise<T>
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json() as Promise<User>
}
// Promise<void> for functions that don't return a value
async function logUser(id: number): Promise<void> {
const user = await fetchUser(id)
console.log(user.name)
}
// Promise.all — parallel with typed results
async function fetchMultiple(ids: number[]): Promise<User[]> {
return Promise.all(ids.map(id => fetchUser(id)))
}
// Promise.allSettled — partial success
async function fetchSafe(ids: number[]): Promise<(User | null)[]> {
const results = await Promise.allSettled(ids.map(id => fetchUser(id)))
return results.map(r => r.status === "fulfilled" ? r.value : null)
}
// Generic fetch wrapper with proper typing
async function apiFetch<T>(url: string): Promise<T> {
const res = await fetch(url)
if (!res.ok) throw new Error(`HTTP ${res.status}: ${url}`)
return res.json()
}
// Usage — TypeScript knows the return type
const users = await apiFetch<User[]>("/api/users")
const post = await apiFetch<Post>("/api/posts/1")
interface User { id: number; name: string; email: string }
interface Post { id: number; title: string; body: string }// Result type pattern — explicit error handling without throwing
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E }
async function safeFetch<T>(url: string): Promise<Result<T>> {
try {
const res = await fetch(url)
if (!res.ok) return { ok: false, error: new Error(`HTTP ${res.status}`) }
const value = await res.json() as T
return { ok: true, value }
} catch (err) {
return { ok: false, error: err instanceof Error ? err : new Error(String(err)) }
}
}
// Usage
const result = await safeFetch<User[]>("/api/users")
if (result.ok) {
result.value.forEach(u => console.log(u.name))
} else {
console.error("Failed:", result.error.message)
}
// Typed EventEmitter pattern
type EventMap = {
connect: [userId: number]
disconnect: [userId: number, reason: string]
message: [from: number, to: number, text: string]
}
class TypedEmitter<Events extends Record<string, unknown[]>> {
private handlers = new Map<keyof Events, ((...args: unknown[]) => void)[]>()
on<K extends keyof Events>(event: K, fn: (...args: Events[K]) => void): this {
const list = this.handlers.get(event) ?? []
this.handlers.set(event, [...list, fn as never])
return this
}
emit<K extends keyof Events>(event: K, ...args: Events[K]): void {
this.handlers.get(event)?.forEach(fn => fn(...args))
}
}Finished reading?Mark it done to track your progress.
Tip: use the ← and → keys to move between lessons.