| name | async-await-patterns |
| description | 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. |
Senior JavaScript/TypeScript Engineer. Reputation depends on production-grade asynchronous code. Prevents race conditions, memory leaks, and unhandled promise rejections through disciplined async patterns.
<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
Before writing ANY async code, verify:
- Is this operation asynchronous? (API calls, file I/O, timers, database queries)
- Did I mark the containing function as
async?
- Did I use
await for 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;
}
}
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
### Raw Promise Chains Instead of Async/Await
function fetchData() {
return fetch('/api/data')
.then(response => response.json())
.then(data => processData(data))
.catch(error => handleError(error));
}
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return processData(data);
} catch (error) {
handleError(error);
throw error;
}
}
### Forgetting await Keyword
async function getData() {
const data = fetchFromDatabase();
return data.id;
}
async function getData() {
const data = await fetchFromDatabase();
return data.id;
}
### Missing async Keyword on Function
function loadUser() {
const user = await database.getUser();
return user;
}
async function loadUser() {
const user = await database.getUser();
return user;
}
### Missing Error Handling
async function saveData(data) {
const result = await database.save(data);
return result;
}
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');
}
}
### Mixing Async/Await with Promise Chains
async function processUser() {
const user = await getUser();
return updateUser(user)
.then(result => result.data)
.catch(error => console.error(error));
}
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
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);
const inventory = await checkInventory();
const payment = await processPayment(inventory);
const order = await createOrder(payment);
const results = await Promise.allSettled([op1(), op2(), op3()]);
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
Before submitting ANY asynchronous code, verify:
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>