typescript-async-patterns

安装量: 35
排名: #19649

安装

npx skills add https://github.com/thebushidocollective/han --skill typescript-async-patterns

Master asynchronous programming patterns in TypeScript, including Promises, async/await, error handling, async iterators, and advanced patterns for building robust async applications.

Promises and async/await

Basic Promise Creation

// 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);
  });

async/await Syntax

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-Safe Promise Wrappers

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);
  }
}

Promise Chaining and Composition

Chaining Promises

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';
    });
}

Composing Async Functions

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');

Error Handling in Async Code

Try-Catch with async/await

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;
  }
}

Error Recovery Patterns

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'}`
  );
}

Typed Error Handling

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;
  }
}

Promise Combinators

Promise.all

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];
}

Promise.allSettled

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 and Promise.any

// 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');
  }
}

Async Iterators and Generators

Basic Async Iterators

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);
  }
}

Async Iterable Data Streams

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}`);
  }
}

Transforming Async Iterables

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
  }
}

Observable Patterns

Simple Observable Implementation

<pre class="langua

返回排行榜