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.
Detects and fixes async/await misuse: removes unnecessary async keywords, converts sequential awaits to parallel Promise.all, and eliminates await on non-Promise values to improve performance and correctness.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
scripts/detect-async-issues.jsscripts/find-sequential-awaits.jsFixes for async/await misuse patterns. Correct async patterns improve code clarity and performance.
| Issue | Priority | Impact |
|---|---|---|
| require-await | P2 | Code smell, minor overhead |
| Sequential async (should be parallel) | P2 | Performance |
| await-thenable | P1 | Logic error indicator |
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.
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.
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:
Why: Awaiting non-Promises is a code smell indicating refactoring remnant or misunderstanding.
node scripts/detect-async-issues.js /path/to/src
node scripts/find-sequential-awaits.js /path/to/file.ts
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)
);
/**
* 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;
}
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!;
}
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'
);
// 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)));
// 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;
}
// 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();
}
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.