Agent skill
async-await-patterns
Use when writing JavaScript or TypeScript code with asynchronous operations, fixing promise-related bugs, or converting callback/promise patterns to async/await. Triggers: 'promise chain', 'unhandled rejection', 'race condition in JS', 'callback hell', 'Promise.all', 'sequential vs parallel async', 'missing await'. Enforces async/await discipline over raw promises.
Install this agent skill to your Project
npx add-skill https://github.com/axiomantic/spellbook/tree/main/skills/async-await-patterns
SKILL.md
<CRITICAL_INSTRUCTION> Use async/await for ALL asynchronous operations instead of raw promises, callbacks, or blocking patterns. This is critical to application stability. NOT optional. NOT negotiable. </CRITICAL_INSTRUCTION>
Invariant Principles
- Explicit async boundary: Function containing await MUST be marked async. Compiler enforces; no exceptions.
- Await ALL promises: Every promise-returning call requires await. Missing await = bug.
- Structured error handling: try-catch wraps async operations. Unhandled rejections crash applications.
- Pattern consistency: async/await XOR promise chains. Never mix in same function.
- Parallelism via combinators: Independent operations use Promise.all/allSettled. Sequential only when dependencies exist.
Required Reasoning
- Is this operation asynchronous? (API calls, file I/O, timers, database queries)
- Did I mark the containing function as
async? - Did I use
awaitfor every promise-returning operation? - Did I add proper try-catch error handling?
- Did I avoid mixing async/await with
.then()/.catch()? - Can independent operations run in parallel with Promise.all?
Now write asynchronous code following this checklist.
Core Pattern
async function operationName(): Promise<ReturnType> {
try {
const result = await asyncOperation();
return result;
} catch (error) {
throw error; // Handle or rethrow with context
}
}
Forbidden Patterns: Quick Reference
| Anti-pattern | Fix |
|---|---|
.then()/.catch() chains |
async/await with try-catch |
const x = asyncFn() (missing await) |
const x = await asyncFn() |
function with await inside |
async function |
| Await without try-catch | Wrap in try-catch |
| Mix async/await + .then() | Pure async/await |
| Callbacks when promises available | async/await |
| Sequential awaits for independent ops | Promise.all |
Forbidden Patterns: Detailed Examples
// BAD
function fetchData() {
return fetch('/api/data')
.then(response => response.json())
.then(data => processData(data))
.catch(error => handleError(error));
}
// CORRECT
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return processData(data);
} catch (error) {
handleError(error);
throw error;
}
}
// BAD - returns Promise instead of value
async function getData() {
const data = fetchFromDatabase(); // Forgot await!
return data.id; // Error: data is a Promise
}
// CORRECT
async function getData() {
const data = await fetchFromDatabase();
return data.id;
}
// BAD - SyntaxError
function loadUser() {
const user = await database.getUser();
return user;
}
// CORRECT
async function loadUser() {
const user = await database.getUser();
return user;
}
// BAD - unhandled promise rejection if save fails
async function saveData(data) {
const result = await database.save(data);
return result;
}
// CORRECT
async function saveData(data) {
try {
const result = await database.save(data);
return result;
} catch (error) {
console.error('Save failed:', error);
throw new Error('Failed to save data');
}
}
// BAD
async function processUser() {
const user = await getUser();
return updateUser(user)
.then(result => result.data)
.catch(error => console.error(error));
}
// CORRECT
async function processUser() {
try {
const user = await getUser();
const result = await updateUser(user);
return result.data;
} catch (error) {
console.error(error);
throw error;
}
}
Parallel vs Sequential
// PARALLEL: independent operations
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);
// SEQUENTIAL: each depends on previous
const inventory = await checkInventory();
const payment = await processPayment(inventory);
const order = await createOrder(payment);
// FAULT-TOLERANT: continue despite failures
const results = await Promise.allSettled([op1(), op2(), op3()]);
// Each result: { status: 'fulfilled', value } or { status: 'rejected', reason }
Complete Real-World Example
async function updateUserProfile(userId: string, updates: ProfileUpdates): Promise<User> {
try {
const user = await database.users.findById(userId);
if (!user) {
throw new Error(`User ${userId} not found`);
}
const validatedUpdates = await validateProfileData(updates);
const updatedUser = await database.users.update(userId, validatedUpdates);
await Promise.all([
notificationService.send(userId, 'Profile updated'),
auditLog.record('profile_update', { userId, updates: validatedUpdates })
]);
return updatedUser;
} catch (error) {
if (error instanceof ValidationError) {
throw new BadRequestError('Invalid profile data', error);
}
if (error instanceof DatabaseError) {
throw new ServiceError('Database operation failed', error);
}
throw new Error(`Failed to update profile: ${error.message}`);
}
}
Recommended: Python Asyncio Pipeline Architecture
For Python scripts with multiple network requests or I/O operations, consider using asyncio with pipeline queues. This is not mandatory, but works well when a script naturally decomposes into producer/consumer stages:
- Network I/O: 3-5 parallel workers
- File I/O: 2-3 parallel workers
- Rate-limited APIs: Single worker with delays
- Shared state: Single worker or locks
This pattern shines when you have distinct stages (fetch, transform, write) that can overlap. For simple scripts with a handful of sequential requests, asyncio.gather or Promise.all is sufficient.
Self-Check
- Did I mark the function as
async? - Did I use
awaitfor EVERY promise-returning operation? - Did I wrap await operations in try-catch blocks?
- Did I avoid using .then()/.catch() chains?
- Did I avoid mixing async/await with promise chains?
- Did I avoid using callbacks when async/await is available?
- Did I consider whether operations can run in parallel with Promise.all()?
- Did I provide meaningful error messages in catch blocks?
- Does error handling preserve error context?
If NO to ANY item above: STOP. Rewrite using proper async/await before proceeding.
<FINAL_EMPHASIS> Use async/await for ALL asynchronous operations. NEVER use raw promise chains when async/await is clearer. NEVER forget the await keyword. NEVER omit error handling. This is critical to code quality and application stability. Non-negotiable. </FINAL_EMPHASIS>
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
spellbook-auditing
Meta-audit skill for spellbook development. Spawns parallel subagents to factcheck docs, optimize instructions, find token savings, and identify MCP candidates. Produces actionable report.
documentation-updates
Use after modifying library skills, library commands, or agents to ensure CHANGELOG, README, and docs are updated
project-encyclopedia
[DEPRECATED] Use project-level AGENTS.md files instead. Previously used for first-session codebase onboarding and persistent glossary creation.
reviewing-impl-plans
Use when reviewing implementation plans before execution. Triggers: 'is this plan solid', 'review the plan', 'check before I start building', 'anything missing from this plan', 'will this plan work', 'audit the implementation plan'. NOT for: reviewing design documents (use reviewing-design-docs) or creating plans (use writing-plans).
session-resume
Session resume protocol and session repairs handling. Loaded when spellbook_session_init returns resume_available: true, or when session_init returns a repairs array. Triggers: 'resume', 'continue', 'where were we', session resume, session repairs.
brainstorming
Use when exploring design approaches, generating ideas, or making architectural decisions. Triggers: 'explore options', 'what are the tradeoffs', 'how should I approach', 'let's think through', 'sketch out an approach', 'I need ideas for', 'how would you structure', 'what are my options'. Also invoked by develop when design decisions are needed.
Didn't find tool you were looking for?