Skip to content

async-await-patterns

Auto-invocation: Your coding assistant will automatically invoke this skill when it detects a matching trigger.

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.

Workflow Diagram

Decision and verification workflow for writing production-grade async/await code in JavaScript and TypeScript. Enforces disciplined async patterns over raw promises.

flowchart TD
    Start([Start]) --> IdentifyAsync{Is Operation Async?}
    IdentifyAsync -->|No| SyncCode[Write Synchronous Code]
    IdentifyAsync -->|Yes| MarkAsync[Mark Function async]
    SyncCode --> End([End])
    MarkAsync --> AddAwait[Add await To All Promises]
    AddAwait --> WrapTryCatch[Wrap In try-catch]
    WrapTryCatch --> CheckDeps{Operations Independent?}
    CheckDeps -->|Yes| UsePromiseAll[Use Promise.all]
    CheckDeps -->|No| SequentialAwait[Sequential await Chain]
    CheckDeps -->|Fault Tolerant| UseAllSettled[Use Promise.allSettled]
    UsePromiseAll --> CheckMixing{Pattern Mixing?}
    SequentialAwait --> CheckMixing
    UseAllSettled --> CheckMixing
    CheckMixing -->|.then/.catch Found| RewriteAwait[Rewrite As async/await]
    CheckMixing -->|Clean| CheckErrors{Error Handling Present?}
    RewriteAwait --> CheckErrors
    CheckErrors -->|No try-catch| AddErrorHandling[Add Typed Error Handling]
    CheckErrors -->|Yes| CheckMissing{Missing await?}
    AddErrorHandling --> CheckMissing
    CheckMissing -->|Promise Not Awaited| FixMissing[Add Missing await]
    CheckMissing -->|All Awaited| SelfCheck{Self-Check Passed?}
    FixMissing --> SelfCheck
    SelfCheck -->|All Items Checked| End
    SelfCheck -->|Items Unchecked| Rewrite[STOP: Rewrite Code]
    Rewrite --> MarkAsync

    style Start fill:#4CAF50,color:#fff
    style End fill:#4CAF50,color:#fff
    style SyncCode fill:#2196F3,color:#fff
    style MarkAsync fill:#2196F3,color:#fff
    style AddAwait fill:#2196F3,color:#fff
    style WrapTryCatch fill:#2196F3,color:#fff
    style UsePromiseAll fill:#2196F3,color:#fff
    style SequentialAwait fill:#2196F3,color:#fff
    style UseAllSettled fill:#2196F3,color:#fff
    style RewriteAwait fill:#2196F3,color:#fff
    style AddErrorHandling fill:#2196F3,color:#fff
    style FixMissing fill:#2196F3,color:#fff
    style Rewrite fill:#2196F3,color:#fff
    style IdentifyAsync fill:#FF9800,color:#fff
    style CheckDeps fill:#FF9800,color:#fff
    style CheckMixing fill:#FF9800,color:#fff
    style CheckErrors fill:#FF9800,color:#fff
    style CheckMissing fill:#FF9800,color:#fff
    style SelfCheck fill:#f44336,color:#fff

Legend

Color Meaning
Green (#4CAF50) Skill invocation
Blue (#2196F3) Command/action
Orange (#FF9800) Decision point
Red (#f44336) Quality gate

Cross-Reference

Node Source Reference
Is Operation Async? Required Reasoning: Step 1
Mark Function async Invariant Principle 1: Explicit async boundary
Add await To All Promises Invariant Principle 2: Await ALL promises
Wrap In try-catch Invariant Principle 3: Structured error handling
Operations Independent? Invariant Principle 5: Parallelism via combinators
Use Promise.all Parallel vs Sequential section
Sequential await Chain Parallel vs Sequential section
Use Promise.allSettled Parallel vs Sequential: Fault-tolerant
Pattern Mixing? Invariant Principle 4: Pattern consistency
Rewrite As async/await Forbidden Pattern 5: Mixing Async/Await with Promise Chains
Error Handling Present? Forbidden Pattern 4: Missing Error Handling
Add Typed Error Handling Complete Real-World Example: catch block
Missing await? Forbidden Pattern 2: Forgetting await Keyword
Self-Check Passed? Self-Check reflection checklist
STOP: Rewrite Code Self-Check: "If NO to ANY item: STOP. Rewrite"

Skill Content

<ROLE>
Senior JavaScript/TypeScript Engineer. Reputation depends on production-grade asynchronous code. Prevents race conditions, memory leaks, and unhandled promise rejections through disciplined async patterns.
</ROLE>

<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

1. **Explicit async boundary**: Function containing await MUST be marked async. Compiler enforces; no exceptions.
2. **Await ALL promises**: Every promise-returning call requires await. Missing await = bug.
3. **Structured error handling**: try-catch wraps async operations. Unhandled rejections crash applications.
4. **Pattern consistency**: async/await XOR promise chains. Never mix in same function.
5. **Parallelism via combinators**: Independent operations use Promise.all/allSettled. Sequential only when dependencies exist.

## Required Reasoning

<analysis>
Before writing ANY async code, verify:

1. Is this operation asynchronous? (API calls, file I/O, timers, database queries)
2. Did I mark the containing function as `async`?
3. Did I use `await` for every promise-returning operation?
4. Did I add proper try-catch error handling?
5. Did I avoid mixing async/await with `.then()/.catch()`?
6. Can independent operations run in parallel with Promise.all?

Now write asynchronous code following this checklist.
</analysis>

## Core Pattern

```typescript
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

<FORBIDDEN pattern="1">
### Raw Promise Chains Instead of Async/Await

```typescript
// 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;
  }
}
```
</FORBIDDEN>

<FORBIDDEN pattern="2">
### Forgetting await Keyword

```typescript
// 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;
}
```
</FORBIDDEN>

<FORBIDDEN pattern="3">
### Missing async Keyword on Function

```typescript
// BAD - SyntaxError
function loadUser() {
  const user = await database.getUser();
  return user;
}

// CORRECT
async function loadUser() {
  const user = await database.getUser();
  return user;
}
```
</FORBIDDEN>

<FORBIDDEN pattern="4">
### Missing Error Handling

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

<FORBIDDEN pattern="5">
### Mixing Async/Await with Promise Chains

```typescript
// 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;
  }
}
```
</FORBIDDEN>

## Parallel vs Sequential

```typescript
// 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

```typescript
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

<reflection>
Before submitting ANY asynchronous code, verify:

- [ ] Did I mark the function as `async`?
- [ ] Did I use `await` for 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.
</reflection>

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