Skip to Content
Grab N GoFetching & Promises

Fetching & Promises

Syntax

Promise Constructor
const p = new Promise<string>((resolve, reject) => { const ok = true; if (ok) resolve("done"); // fulfills with value else reject("fail"); // rejects with reason });
Then / Catch / Finally
fetch("/api/data") .then((res) => res.json()) // handle fulfilled .catch((err) => console.error(err)) // handle rejected .finally(() => cleanup()); // always runs
Async / Await
async function getData(): Promise<string> { const res = await fetch("/api/data"); const data: string = await res.json(); return data; }
Fetch Options
const controller = new AbortController(); fetch("/api/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Widget" }), signal: controller.signal, });
Response Methods
const res = await fetch("/api"); await res.json(); // parse JSON body await res.text(); // plain text await res.blob(); // binary data res.ok; // true if 200-299 res.status; // 404, 500, etc.
Promise Static Methods
Promise.all([p1, p2]); // all must fulfill Promise.allSettled([p1, p2]); // wait for all, never rejects Promise.race([p1, p2]); // first to settle wins Promise.any([p1, p2]); // first to fulfill wins

Beginner Patterns

GET Request
async function getUser(id: number) { const res = await fetch(`/api/users/${id}`); if (!res.ok) throw new Error(`Status: ${res.status}`); const user: User = await res.json(); return user; }
POST Request
async function createUser(name: string) { const res = await fetch("/api/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }), }); return res.json() as Promise<User>; }
Try / Catch
async function loadData(url: string) { try { const res = await fetch(url); return await res.json(); } catch (err) { console.error("Fetch failed:", err); return null; } }
Typing a Response
interface User { id: number; name: string; email: string; } const res = await fetch("/api/user"); const user = (await res.json()) as User; // user.name is now typed
Query Params
async function search(term: string, page: number) { const params = new URLSearchParams({ q: term, page: String(page), }); const res = await fetch(`/api/search?${params}`); return res.json(); }
Chaining Promises
fetch("/api/user/1") .then((res) => res.json()) .then((user: User) => fetch(`/api/posts?author=${user.id}`)) .then((res) => res.json()) .then((posts: Post[]) => console.log(posts));

Intermediate Patterns

Parallel Requests
async function getDashboard(userId: number) { const [user, posts, stats] = await Promise.all([ fetch(`/api/users/${userId}`).then((r) => r.json()), fetch(`/api/posts?author=${userId}`).then((r) => r.json()), fetch(`/api/stats/${userId}`).then((r) => r.json()), ]); return { user, posts, stats } as Dashboard; }
Abort Controller
const controller = new AbortController(); fetch("/api/slow", { signal: controller.signal }) .then((res) => res.json()) .catch((err) => { if (err.name === "AbortError") return; // expected throw err; }); controller.abort(); // cancels the request
Timeout
async function fetchWithTimeout(url: string, ms: number) { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), ms); try { const res = await fetch(url, { signal: controller.signal }); return await res.json(); } finally { clearTimeout(id); } }
Retry Logic
async function fetchRetry(url: string, retries = 3) { for (let i = 0; i < retries; i++) { try { const res = await fetch(url); if (res.ok) return res.json(); } catch { if (i === retries - 1) throw new Error("Max retries"); } await new Promise((r) => setTimeout(r, 1000 * 2 ** i)); } throw new Error("Max retries"); }
AllSettled
const results = await Promise.allSettled([ fetch("/api/a").then((r) => r.json()), fetch("/api/b").then((r) => r.json()), ]); results.forEach((r) => { if (r.status === "fulfilled") console.log(r.value); if (r.status === "rejected") console.error(r.reason); });
Generic Fetcher
async function fetcher<T>(url: string): Promise<T> { const res = await fetch(url); if (!res.ok) throw new Error(res.statusText); return res.json() as Promise<T>; } const user = await fetcher<User>("/api/user/1");

Advanced Patterns

Request Interceptor
async function authFetch(url: string, opts?: RequestInit) { let token = getToken(); let res = await fetch(url, { ...opts, headers: { ...opts?.headers, Authorization: `Bearer ${token}` }, }); if (res.status === 401) { await refreshToken(); token = getToken(); res = await fetch(url, { ...opts, headers: { ...opts?.headers, Authorization: `Bearer ${token}` }, }); } return res; }
Race with Timeout
async function raceTimeout<T>(promise: Promise<T>, ms: number) { const timeout = new Promise<never>((_, reject) => setTimeout(() => reject(new Error("Timeout")), ms) ); return Promise.race([promise, timeout]); } const data = await raceTimeout(fetch("/api"), 5000);
Streaming Response
const res = await fetch("/api/stream"); const reader = res.body!.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; console.log(decoder.decode(value)); // chunk }
Typed Error Handling
class FetchError extends Error { constructor(public status: number, message: string) { super(message); } } async function safeFetch<T>(url: string): Promise<T> { const res = await fetch(url); if (!res.ok) throw new FetchError(res.status, res.statusText); return res.json() as Promise<T>; }
Sequential Pipeline
async function pipeline(ids: number[]) { const results: User[] = []; for (const id of ids) { const user = await fetcher<User>(`/api/users/${id}`); results.push(user); // sequential, respects rate limits } return results; }
Batch with Concurrency
async function batch<T>(urls: string[], limit = 3) { const results: T[] = []; for (let i = 0; i < urls.length; i += limit) { const chunk = urls.slice(i, i + limit); const data = await Promise.all( chunk.map((url) => fetcher<T>(url)) ); results.push(...data); } return results; }
Deduped Fetch
const cache = new Map<string, Promise<unknown>>(); function dedupeFetch<T>(url: string): Promise<T> { if (!cache.has(url)) { cache.set(url, fetcher<T>(url).finally(() => cache.delete(url))); } return cache.get(url) as Promise<T>; } // concurrent calls to same URL share one request
Last updated on