Use when typeScript async patterns including Promises, async/await, and async iterators with proper typing. Use when writing asynchronous TypeScript code.
Generates robust asynchronous TypeScript code using Promises, async/await, and error handling patterns.
npx claudepluginhub thebushidocollective/hanThis skill is limited to using the following tools:
Master asynchronous programming patterns in TypeScript, including Promises, async/await, error handling, async iterators, and advanced patterns for building robust async applications.
// Creating a Promise
function delay(ms: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
// Promise with value
function fetchUserData(userId: string): Promise<User> {
return new Promise((resolve, reject) => {
// Simulated API call
setTimeout(() => {
if (userId) {
resolve({ id: userId, name: 'John Doe' });
} else {
reject(new Error('Invalid user ID'));
}
}, 1000);
});
}
// Using the Promise
fetchUserData('123')
.then((user) => {
console.log(user.name);
})
.catch((error) => {
console.error('Error:', error.message);
});
interface User {
id: string;
name: string;
email: string;
}
interface Post {
id: string;
userId: string;
title: string;
content: string;
}
// Async function declaration
async function getUserPosts(userId: string): Promise<Post[]> {
try {
const user = await fetchUserData(userId);
const posts = await fetchPostsByUser(user.id);
return posts;
} catch (error) {
console.error('Failed to fetch user posts:', error);
throw error;
}
}
// Async arrow function
const getUserProfile = async (userId: string): Promise<User> => {
const user = await fetchUserData(userId);
return user;
};
// Using async/await
async function main() {
const posts = await getUserPosts('123');
console.log(`Found ${posts.length} posts`);
}
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
async function safeAsync<T>(
promise: Promise<T>
): Promise<Result<T>> {
try {
const data = await promise;
return { success: true, data };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error(String(error)),
};
}
}
// Usage
async function example() {
const result = await safeAsync(fetchUserData('123'));
if (result.success) {
console.log(result.data.name);
} else {
console.error(result.error.message);
}
}
interface ApiResponse<T> {
data: T;
status: number;
}
function fetchData<T>(url: string): Promise<ApiResponse<T>> {
return fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => ({
data,
status: 200,
}));
}
// Chaining multiple async operations
function processUserData(userId: string): Promise<string> {
return fetchUserData(userId)
.then((user) => fetchPostsByUser(user.id))
.then((posts) => posts.filter((post) => post.title.includes('TypeScript')))
.then((filteredPosts) => `Found ${filteredPosts.length} TypeScript posts`)
.catch((error) => {
console.error('Error in chain:', error);
return 'Failed to process user data';
});
}
type AsyncFunction<T, R> = (input: T) => Promise<R>;
function pipe<T, A, B>(
fn1: AsyncFunction<T, A>,
fn2: AsyncFunction<A, B>
): AsyncFunction<T, B> {
return async (input: T) => {
const result1 = await fn1(input);
return fn2(result1);
};
}
// Usage
const getUserId = async (username: string): Promise<string> => {
// Look up user ID
return '123';
};
const getUserData = async (userId: string): Promise<User> => {
return fetchUserData(userId);
};
const getUserByUsername = pipe(getUserId, getUserData);
// Use the composed function
const user = await getUserByUsername('johndoe');
class ApiError extends Error {
constructor(
message: string,
public statusCode: number,
public response?: unknown
) {
super(message);
this.name = 'ApiError';
}
}
async function fetchWithErrorHandling<T>(url: string): Promise<T> {
try {
const response = await fetch(url);
if (!response.ok) {
throw new ApiError(
`HTTP error! status: ${response.status}`,
response.status
);
}
const data = await response.json();
return data;
} catch (error) {
if (error instanceof ApiError) {
console.error(`API Error ${error.statusCode}: ${error.message}`);
} else if (error instanceof TypeError) {
console.error('Network error:', error.message);
} else {
console.error('Unknown error:', error);
}
throw error;
}
}
async function fetchWithFallback<T>(
primaryUrl: string,
fallbackUrl: string
): Promise<T> {
try {
return await fetchWithErrorHandling<T>(primaryUrl);
} catch (error) {
console.warn('Primary fetch failed, trying fallback');
return await fetchWithErrorHandling<T>(fallbackUrl);
}
}
// Multiple fallbacks
async function fetchWithMultipleFallbacks<T>(
urls: string[]
): Promise<T> {
let lastError: Error | undefined;
for (const url of urls) {
try {
return await fetchWithErrorHandling<T>(url);
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
console.warn(`Failed to fetch from ${url}, trying next...`);
}
}
throw new Error(
`All fetches failed. Last error: ${lastError?.message ?? 'Unknown'}`
);
}
class ValidationError extends Error {
constructor(message: string, public field: string) {
super(message);
this.name = 'ValidationError';
}
}
class NetworkError extends Error {
constructor(message: string, public url: string) {
super(message);
this.name = 'NetworkError';
}
}
type AppError = ValidationError | NetworkError | Error;
async function handleUserUpdate(userId: string, data: unknown): Promise<void> {
try {
await validateUserData(data);
await updateUser(userId, data);
} catch (error) {
if (error instanceof ValidationError) {
console.error(`Validation error in field ${error.field}: ${error.message}`);
} else if (error instanceof NetworkError) {
console.error(`Network error for ${error.url}: ${error.message}`);
} else if (error instanceof Error) {
console.error(`Unexpected error: ${error.message}`);
}
throw error;
}
}
interface UserData {
profile: User;
posts: Post[];
comments: Comment[];
}
async function fetchUserDataParallel(userId: string): Promise<UserData> {
const [profile, posts, comments] = await Promise.all([
fetchUserData(userId),
fetchPostsByUser(userId),
fetchCommentsByUser(userId),
]);
return { profile, posts, comments };
}
// Type-safe Promise.all with tuple
async function fetchMultipleResources() {
const [users, posts, settings] = await Promise.all([
fetchUsers(), // Promise<User[]>
fetchPosts(), // Promise<Post[]>
fetchSettings(), // Promise<Settings>
] as const);
// TypeScript infers correct types
const firstUser: User = users[0];
const firstPost: Post = posts[0];
}
interface SettledResult<T> {
status: 'fulfilled' | 'rejected';
value?: T;
reason?: Error;
}
async function fetchAllUserData(
userIds: string[]
): Promise<Array<SettledResult<User>>> {
const results = await Promise.allSettled(
userIds.map((id) => fetchUserData(id))
);
return results.map((result) => {
if (result.status === 'fulfilled') {
return { status: 'fulfilled', value: result.value };
} else {
return { status: 'rejected', reason: result.reason };
}
});
}
// Usage
const results = await fetchAllUserData(['1', '2', '3']);
const successful = results.filter((r) => r.status === 'fulfilled');
const failed = results.filter((r) => r.status === 'rejected');
console.log(`${successful.length} succeeded, ${failed.length} failed`);
// Promise.race - first to settle (fulfill or reject)
async function fetchWithTimeout<T>(
promise: Promise<T>,
timeoutMs: number
): Promise<T> {
const timeout = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error('Operation timed out')), timeoutMs);
});
return Promise.race([promise, timeout]);
}
// Usage
const data = await fetchWithTimeout(fetchUserData('123'), 5000);
// Promise.any - first to fulfill (ignores rejections)
async function fetchFromFastestServer<T>(
urls: string[]
): Promise<T> {
const fetchPromises = urls.map((url) => fetchWithErrorHandling<T>(url));
try {
return await Promise.any(fetchPromises);
} catch (error) {
throw new Error('All servers failed to respond');
}
}
interface AsyncIteratorResult<T> {
value: T;
done: boolean;
}
async function* numberGenerator(max: number): AsyncGenerator<number> {
for (let i = 0; i < max; i++) {
await delay(100);
yield i;
}
}
// Using async iterator
async function consumeNumbers() {
for await (const num of numberGenerator(5)) {
console.log(num);
}
}
interface DataChunk {
data: string;
timestamp: number;
}
class AsyncDataStream implements AsyncIterable<DataChunk> {
constructor(private source: string[]) {}
async *[Symbol.asyncIterator](): AsyncGenerator<DataChunk> {
for (const data of this.source) {
await delay(100);
yield {
data,
timestamp: Date.now(),
};
}
}
}
// Usage
async function processStream() {
const stream = new AsyncDataStream(['chunk1', 'chunk2', 'chunk3']);
for await (const chunk of stream) {
console.log(`Received at ${chunk.timestamp}: ${chunk.data}`);
}
}
async function* mapAsync<T, R>(
iterable: AsyncIterable<T>,
mapper: (value: T) => Promise<R> | R
): AsyncGenerator<R> {
for await (const value of iterable) {
yield await mapper(value);
}
}
async function* filterAsync<T>(
iterable: AsyncIterable<T>,
predicate: (value: T) => Promise<boolean> | boolean
): AsyncGenerator<T> {
for await (const value of iterable) {
if (await predicate(value)) {
yield value;
}
}
}
// Usage
async function transformData() {
const stream = new AsyncDataStream(['1', '2', '3', '4', '5']);
const numbers = mapAsync(stream, (chunk) => parseInt(chunk.data));
const evenNumbers = filterAsync(numbers, (n) => n % 2 === 0);
for await (const num of evenNumbers) {
console.log(num); // 2, 4
}
}
type Observer<T> = (value: T) => void;
type Unsubscribe = () => void;
class Observable<T> {
private observers: Set<Observer<T>> = new Set();
subscribe(observer: Observer<T>): Unsubscribe {
this.observers.add(observer);
return () => {
this.observers.delete(observer);
};
}
next(value: T): void {
this.observers.forEach((observer) => observer(value));
}
pipe<R>(operator: (obs: Observable<T>) => Observable<R>): Observable<R> {
return operator(this);
}
}
// Operator
function map<T, R>(mapper: (value: T) => R) {
return (source: Observable<T>): Observable<R> => {
const result = new Observable<R>();
source.subscribe((value) => {
result.next(mapper(value));
});
return result;
};
}
// Usage
const numbers = new Observable<number>();
const doubled = numbers.pipe(map((n) => n * 2));
doubled.subscribe((value) => console.log(value));
numbers.next(5); // 10
interface AsyncObserver<T> {
next: (value: T) => Promise<void> | void;
error?: (error: Error) => Promise<void> | void;
complete?: () => Promise<void> | void;
}
class AsyncObservable<T> {
private observers: Set<AsyncObserver<T>> = new Set();
subscribe(observer: AsyncObserver<T>): Unsubscribe {
this.observers.add(observer);
return () => {
this.observers.delete(observer);
};
}
async next(value: T): Promise<void> {
await Promise.all(
Array.from(this.observers).map((obs) => obs.next(value))
);
}
async error(error: Error): Promise<void> {
await Promise.all(
Array.from(this.observers)
.filter((obs) => obs.error)
.map((obs) => obs.error!(error))
);
}
async complete(): Promise<void> {
await Promise.all(
Array.from(this.observers)
.filter((obs) => obs.complete)
.map((obs) => obs.complete!())
);
}
}
async function fetchWithCancellation(
url: string,
signal: AbortSignal
): Promise<Response> {
const response = await fetch(url, { signal });
if (signal.aborted) {
throw new Error('Request was cancelled');
}
return response;
}
// Usage
const controller = new AbortController();
// Start fetch
const fetchPromise = fetchWithCancellation(
'https://api.example.com/data',
controller.signal
);
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);
try {
const response = await fetchPromise;
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
console.log('Request was cancelled');
}
}
interface CancellablePromise<T> extends Promise<T> {
cancel: () => void;
}
function makeCancellable<T>(
promise: Promise<T>
): CancellablePromise<T> {
let cancelled = false;
const wrappedPromise = new Promise<T>((resolve, reject) => {
promise
.then((value) => {
if (!cancelled) {
resolve(value);
}
})
.catch((error) => {
if (!cancelled) {
reject(error);
}
});
}) as CancellablePromise<T>;
wrappedPromise.cancel = () => {
cancelled = true;
};
return wrappedPromise;
}
// Usage
const cancellable = makeCancellable(fetchUserData('123'));
setTimeout(() => {
cancellable.cancel();
}, 1000);
interface RetryOptions {
maxAttempts: number;
baseDelay: number;
maxDelay: number;
backoffMultiplier: number;
}
async function retry<T>(
fn: () => Promise<T>,
options: RetryOptions
): Promise<T> {
let lastError: Error | undefined;
for (let attempt = 0; attempt < options.maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
if (attempt < options.maxAttempts - 1) {
const delayMs = Math.min(
options.baseDelay * Math.pow(options.backoffMultiplier, attempt),
options.maxDelay
);
console.log(`Attempt ${attempt + 1} failed, retrying in ${delayMs}ms`);
await delay(delayMs);
}
}
}
throw new Error(
`Failed after ${options.maxAttempts} attempts: ${lastError?.message ?? 'Unknown error'}`
);
}
// Usage
const data = await retry(
() => fetchUserData('123'),
{
maxAttempts: 3,
baseDelay: 1000,
maxDelay: 5000,
backoffMultiplier: 2,
}
);
type RetryPredicate = (error: Error, attempt: number) => boolean;
async function retryWhen<T>(
fn: () => Promise<T>,
shouldRetry: RetryPredicate,
maxAttempts: number
): Promise<T> {
let lastError: Error | undefined;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
if (attempt < maxAttempts - 1 && shouldRetry(lastError, attempt)) {
await delay(1000 * (attempt + 1));
} else {
throw lastError;
}
}
}
throw lastError!;
}
// Usage: Only retry on network errors
const data = await retryWhen(
() => fetchUserData('123'),
(error) => error instanceof NetworkError,
3
);
class TimeoutError extends Error {
constructor(message: string) {
super(message);
this.name = 'TimeoutError';
}
}
async function withTimeout<T>(
promise: Promise<T>,
timeoutMs: number,
message = 'Operation timed out'
): Promise<T> {
let timeoutId: NodeJS.Timeout;
const timeoutPromise = new Promise<never>((_, reject) => {
timeoutId = setTimeout(() => {
reject(new TimeoutError(message));
}, timeoutMs);
});
try {
return await Promise.race([promise, timeoutPromise]);
} finally {
clearTimeout(timeoutId!);
}
}
// Combined retry with timeout
async function retryWithTimeout<T>(
fn: () => Promise<T>,
options: RetryOptions & { timeout: number }
): Promise<T> {
return retry(
() => withTimeout(fn(), options.timeout),
options
);
}
// Extract Promise type
type Awaited<T> = T extends Promise<infer U> ? U : T;
// Usage
type UserPromise = Promise<User>;
type UserType = Awaited<UserPromise>; // User
// For functions
type ReturnTypeAsync<T extends (...args: any) => any> =
Awaited<ReturnType<T>>;
async function getUser(): Promise<User> {
return { id: '1', name: 'John', email: 'john@example.com' };
}
type UserFromFunction = ReturnTypeAsync<typeof getUser>; // User
type AsyncFn<Args extends any[], R> = (...args: Args) => Promise<R>;
// Type-safe async pipe
function composeAsync<A, B, C>(
f: AsyncFn<[A], B>,
g: AsyncFn<[B], C>
): AsyncFn<[A], C> {
return async (a: A) => {
const b = await f(a);
return g(b);
};
}
// Memoize async function
function memoizeAsync<Args extends any[], R>(
fn: AsyncFn<Args, R>
): AsyncFn<Args, R> {
const cache = new Map<string, Promise<R>>();
return async (...args: Args) => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key)!;
}
const promise = fn(...args);
cache.set(key, promise);
try {
return await promise;
} catch (error) {
cache.delete(key);
throw error;
}
};
}
Always Handle Errors: Use try-catch with async/await or .catch() with Promises. Never let errors go unhandled in async code.
Avoid Mixing Paradigms: Choose either Promise chains or async/await for a given flow. Mixing them makes code harder to read and maintain.
Use Promise.all for Parallel Operations: When operations are independent, use Promise.all to run them in parallel rather than sequentially.
Type Promise Return Values: Always explicitly type the return value of async functions and Promises for better type safety and IDE support.
Handle Race Conditions: Be careful with shared state in async code. Use proper synchronization or immutable data structures.
Set Timeouts for Network Requests: Always add timeouts to prevent hanging requests. Use AbortController or Promise.race.
Implement Proper Cleanup: Use finally blocks or try/finally to ensure cleanup code runs regardless of success or failure.
Avoid Async in Constructors: Constructors cannot be async. Use factory functions or initialization methods instead.
Use AbortController for Cancellation: Prefer standard AbortController over custom cancellation for better browser/Node.js compatibility.
Document Async Behavior: Clearly document what async functions do, what they return, and what errors they might throw.
Forgetting await: Forgetting await on async functions returns a Promise instead of the resolved value, causing type errors and bugs.
Sequential When Parallel Is Better: Using await in loops when operations could run in parallel leads to poor performance.
Unhandled Promise Rejections: Not catching errors in Promises or async functions can crash Node.js applications or cause silent failures.
Floating Promises: Not awaiting or handling Promises (fire-and-forget) can cause unhandled rejections and race conditions.
Promise Constructor Anti-pattern: Wrapping already-promisified functions in new Promise is unnecessary and adds complexity.
Async IIFE Mistakes: Forgetting to await or handle errors from immediately-invoked async functions causes silent failures.
Wrong Error Type Assumptions: Assuming all errors are Error instances. Use proper type checking or type guards.
Memory Leaks in Async Iterators: Not properly cleaning up async iterators can cause memory leaks, especially with infinite streams.
Ignoring Cancellation: Not implementing cancellation for long-running operations wastes resources and degrades user experience.
Over-Using Async: Making everything async when synchronous alternatives exist adds unnecessary complexity and performance overhead.
Use TypeScript async patterns when you need to:
This skill is essential for full-stack developers, frontend engineers working with APIs, backend developers building services, and anyone building modern JavaScript/TypeScript applications.
Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.