install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/ts-async-patterns" ~/.claude/skills/intense-visions-harness-engineering-ts-async-patterns && rm -rf "$T"
manifest:
agents/skills/claude-code/ts-async-patterns/SKILL.mdsource content
TypeScript Async Patterns
Type async/await, Promise chains, and concurrent patterns correctly in TypeScript
When to Use
- Typing async functions and their return values
- Handling errors in async code with proper types
- Running concurrent operations with Promise.all, Promise.allSettled, Promise.race
- Creating typed async utilities and middleware
Instructions
- Async function return types — always return
:Promise<T>
async function getUser(id: string): Promise<User> { const response = await fetch(`/api/users/${id}`); return response.json(); }
- Type Promise.all — preserves tuple types:
const [user, posts, settings] = await Promise.all([ getUser(userId), // Promise<User> getUserPosts(userId), // Promise<Post[]> getSettings(), // Promise<Settings> ]); // Types: [User, Post[], Settings]
- Promise.allSettled — returns settled results with status:
const results = await Promise.allSettled([fetchUser(), fetchPosts()]); for (const result of results) { if (result.status === 'fulfilled') { console.log(result.value); // Type: User | Post[] (union of all types) } else { console.log(result.reason); // Type: any } }
- Promise.race — returns the type of the first resolved:
const result = await Promise.race([ fetchData(), // Promise<Data> timeout(5000), // Promise<never> ]); // Type: Data (never is absorbed)
- Typed error handling — catch blocks receive
:unknown
async function fetchUser(id: string): Promise<User> { try { const res = await fetch(`/api/users/${id}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); return await res.json(); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to fetch user: ${error.message}`); } throw error; } }
- Result type pattern — avoid exceptions for expected failures:
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}`) }; return { ok: true, value: await res.json() }; } catch (error) { return { ok: false, error: error instanceof Error ? error : new Error(String(error)) }; } }
- Typed retry logic:
async function retry<T>( fn: () => Promise<T>, options: { retries: number; delay: number } ): Promise<T> { for (let i = 0; i <= options.retries; i++) { try { return await fn(); } catch (error) { if (i === options.retries) throw error; await new Promise((r) => setTimeout(r, options.delay * (i + 1))); } } throw new Error('Unreachable'); }
- Concurrent execution with concurrency limit:
async function mapConcurrent<T, R>( items: T[], fn: (item: T) => Promise<R>, concurrency: number ): Promise<R[]> { const results: R[] = []; const executing = new Set<Promise<void>>(); for (const item of items) { const p = fn(item).then((result) => { results.push(result); }); executing.add(p); p.finally(() => executing.delete(p)); if (executing.size >= concurrency) { await Promise.race(executing); } } await Promise.all(executing); return results; }
- AsyncIterable for streaming data:
async function* paginate<T>( fetchPage: (cursor?: string) => Promise<{ data: T[]; nextCursor?: string }> ): AsyncGenerator<T> { let cursor: string | undefined; do { const page = await fetchPage(cursor); yield* page.data; cursor = page.nextCursor; } while (cursor); } for await (const user of paginate(fetchUsers)) { console.log(user); }
Details
TypeScript types async code through the
Promise<T> generic type. The await keyword unwraps Promise<T> to T, and async functions always return Promise<T>.
utility type: Recursively unwraps nested Promises. Awaited<T>
Awaited<Promise<Promise<string>>> is string. Useful for typing the result of Promise.all and similar utilities.
Error typing limitations:
catch blocks and .catch() callbacks receive unknown (with useUnknownInCatchVariables). TypeScript cannot track which errors a function might throw. This is by design — any function can throw any error. Use the Result pattern for typed error handling.
vs void
in async: An async function with no return statement returns undefined
Promise<void>, not Promise<undefined>. These are subtly different — void means "ignore the return value," undefined means "the value is undefined."
Unhandled rejection risks: Forgetting to
await a Promise means errors are unhandled. TypeScript does not warn about unawaited promises by default — use the @typescript-eslint/no-floating-promises rule.
Trade-offs:
fails fast — if one promise rejects, the entire result rejects. UsePromise.all
when partial success is acceptablePromise.allSettled- Async generators are powerful but hard to debug and have poor error propagation
- The Result pattern avoids exceptions but adds verbosity — use it for expected failures (API errors), not unexpected ones (null references)
Source
https://typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.