Claude Code Plugins

Community-maintained marketplace

Feedback

Fixes async/await pattern issues including unnecessary async, sequential operations that should be parallel, and awaiting non-promises. Use when encountering require-await, await-thenable, or optimizing Promise performance.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name async-patterns
description Fixes async/await pattern issues including unnecessary async, sequential operations that should be parallel, and awaiting non-promises. Use when encountering require-await, await-thenable, or optimizing Promise performance.

Async Pattern Fixes

Fixes for async/await misuse patterns. Correct async patterns improve code clarity and performance.

Quick Start

  1. Identify the async issue type (unnecessary async, sequential awaits, await non-promise)
  2. Understand the data dependencies
  3. Apply the appropriate pattern
  4. Verify correctness with tests

Priority Matrix

Issue Priority Impact
require-await P2 Code smell, minor overhead
Sequential async (should be parallel) P2 Performance
await-thenable P1 Logic error indicator

Workflows

Unnecessary Async (#1 - 360 occurrences)

Detection: @typescript-eslint/require-await

Pattern: Function marked async but contains no await.

// PROBLEM - async with no await
async function getData(): Promise<Data> {
  return fetchFromCache(); // fetchFromCache returns Promise
}

Fix Decision Tree:

Scenario Fix
Function returns Promise directly Remove async, return Promise
Interface requires async signature Add eslint-disable with comment
Await will be added later Add the await now or remove async

Fix Strategy 1: Remove async keyword.

// SOLUTION - remove async, return Promise directly
function getData(): Promise<Data> {
  return fetchFromCache();
}

Fix Strategy 2: Interface conformance (document why).

// SOLUTION - async required by interface
// Note: This handler can become async when database is migrated
async function getData(): Promise<Data> {
  // async required by IDataProvider interface - will use await after migration
  return fetchFromCache();
}

Why: Removing unnecessary async is cleaner. The async keyword creates an implicit Promise wrapper which adds overhead.


Sequential Async Operations (#21 - 4 occurrences)

Detection: Multiple consecutive await statements on independent operations.

Pattern: Sequential awaits when operations have no data dependency.

// PROBLEM - sequential (slow)
const user = await getUser(id);
const orders = await getOrders(id);
const preferences = await getPreferences(id);
// Total time: getUser + getOrders + getPreferences

Fix Strategy 1: Promise.all for all-or-nothing.

// SOLUTION - parallel with Promise.all (fast)
const [user, orders, preferences] = await Promise.all([
  getUser(id),
  getOrders(id),
  getPreferences(id),
]);
// Total time: max(getUser, getOrders, getPreferences)

Fix Strategy 2: Promise.allSettled for partial success.

// SOLUTION - when partial failure is acceptable
const results = await Promise.allSettled([
  getUser(id),
  getOrders(id),
  getPreferences(id),
]);

const user = results[0].status === 'fulfilled' ? results[0].value : null;
const orders = results[1].status === 'fulfilled' ? results[1].value : [];
const preferences = results[2].status === 'fulfilled'
  ? results[2].value
  : defaultPreferences;

Decision Guide:

Scenario Use
All must succeed Promise.all
Partial success OK Promise.allSettled
First success wins Promise.race
First settled (success/fail) Promise.any

Dependency Analysis:

Before converting to parallel, verify no dependencies:

// CANNOT parallelize - orders depends on user
const user = await getUser(id);
const orders = await getOrders(user.accountId); // Depends on user

// CAN parallelize - independent lookups
const user = await getUser(userId);
const product = await getProduct(productId); // Independent

Why: Sequential awaits add latency unnecessarily. Promise.all runs concurrently.


Await Non-Promise (#18 - 4 occurrences)

Detection: @typescript-eslint/await-thenable

Pattern: Using await on a non-Promise value.

// PROBLEM - awaiting non-Promise
const config = await loadConfig();  // loadConfig returns Config, not Promise<Config>

Fix Strategy 1: Remove await (most common).

// SOLUTION - remove await
const config = loadConfig();

Fix Strategy 2: Make value a Promise (if async needed).

// SOLUTION - if async wrapping is intentional
const config = await Promise.resolve(loadConfig());

Diagnostic Questions:

  1. Was this function previously async? (Refactoring remnant)
  2. Should this function be async? (Missing async keyword)
  3. Is this intentional sync-to-async conversion? (Use Promise.resolve)

Why: Awaiting non-Promises is a code smell indicating refactoring remnant or misunderstanding.


Scripts

Detect Async Issues

node scripts/detect-async-issues.js /path/to/src

Find Parallelizable Awaits

node scripts/find-sequential-awaits.js /path/to/file.ts

Advanced Patterns

Controlled Concurrency

When you need parallelism but with limits:

/**
 * Process items with controlled concurrency
 * @param items - Items to process
 * @param concurrency - Max concurrent operations
 * @param processor - Async processor function
 */
async function processWithConcurrency<T, R>(
  items: T[],
  concurrency: number,
  processor: (item: T) => Promise<R>
): Promise<R[]> {
  const results: R[] = [];
  const executing = new Set<Promise<void>>();

  for (const item of items) {
    const promise = processor(item).then(result => {
      results.push(result);
      executing.delete(promise);
    });

    executing.add(promise);

    if (executing.size >= concurrency) {
      await Promise.race(executing);
    }
  }

  await Promise.all(executing);
  return results;
}

// Usage
const results = await processWithConcurrency(
  userIds,
  5, // Max 5 concurrent requests
  id => fetchUser(id)
);

Sequential with Early Exit

/**
 * Process sequentially, stop on first success
 */
async function findFirst<T, R>(
  items: T[],
  finder: (item: T) => Promise<R | null>
): Promise<R | null> {
  for (const item of items) {
    const result = await finder(item);
    if (result !== null) {
      return result;
    }
  }
  return null;
}

Retry with Exponential Backoff

interface RetryOptions {
  maxAttempts?: number;
  initialDelay?: number;
  maxDelay?: number;
  backoffFactor?: number;
}

async function withRetry<T>(
  operation: () => Promise<T>,
  options: RetryOptions = {}
): Promise<T> {
  const {
    maxAttempts = 3,
    initialDelay = 1000,
    maxDelay = 30000,
    backoffFactor = 2,
  } = options;

  let lastError: Error;
  let delay = initialDelay;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error instanceof Error ? error : new Error(String(error));

      if (attempt === maxAttempts) {
        break;
      }

      console.warn(`Attempt ${attempt} failed, retrying in ${delay}ms`, {
        error: lastError.message,
      });

      await new Promise(resolve => setTimeout(resolve, delay));
      delay = Math.min(delay * backoffFactor, maxDelay);
    }
  }

  throw lastError!;
}

Timeout Wrapper

async function withTimeout<T>(
  promise: Promise<T>,
  timeoutMs: number,
  message = 'Operation timed out'
): Promise<T> {
  const timeoutPromise = new Promise<never>((_, reject) => {
    setTimeout(() => reject(new Error(message)), timeoutMs);
  });

  return Promise.race([promise, timeoutPromise]);
}

// Usage
const user = await withTimeout(
  fetchUser(id),
  5000,
  'User fetch timed out after 5s'
);

Anti-Patterns

Async Array Methods

// WRONG - forEach doesn't await
users.forEach(async user => {
  await saveUser(user); // These run in parallel, uncontrolled
});

// RIGHT - for...of for sequential
for (const user of users) {
  await saveUser(user);
}

// RIGHT - Promise.all for parallel
await Promise.all(users.map(user => saveUser(user)));

Mixing Callbacks and Promises

// WRONG - mixing paradigms
function loadData(callback) {
  fetchData().then(data => {
    callback(null, data);
  }).catch(err => {
    callback(err);
  });
}

// RIGHT - promisify or stay consistent
async function loadData(): Promise<Data> {
  return fetchData();
}

// Or if you need callback compatibility
function loadData(callback?: (err: Error | null, data?: Data) => void): Promise<Data> {
  const promise = fetchData();
  if (callback) {
    promise.then(data => callback(null, data)).catch(callback);
  }
  return promise;
}

Creating Unnecessary Promise Wrappers

// WRONG - wrapping existing Promise
async function getData(): Promise<Data> {
  return new Promise((resolve, reject) => {
    fetchData().then(resolve).catch(reject);
  });
}

// RIGHT - return directly
function getData(): Promise<Data> {
  return fetchData();
}