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.

5 stars

Best use case

async-await-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

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.

Teams using async-await-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/async-await-patterns/SKILL.md --create-dirs "https://raw.githubusercontent.com/axiomantic/spellbook/main/skills/async-await-patterns/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/async-await-patterns/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How async-await-patterns Compares

Feature / Agentasync-await-patternsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

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.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

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

Related Skills

writing-skills

5
from axiomantic/spellbook

Use when creating new skills, editing existing skills, or verifying skills work before deployment. Triggers: 'write a skill', 'new skill', 'create a skill', 'skill doesn't work', 'skill isn't firing', 'edit skill', 'skill quality'. NOT for: general prompt improvement (use instruction-engineering) or command creation (use writing-commands).

writing-plans

5
from axiomantic/spellbook

Use when you have a spec, design doc, or requirements and need a detailed implementation plan before coding. Triggers: 'write a plan', 'create implementation plan', 'plan this out', 'break this down into steps', 'convert design to tasks', 'implementation order'. Also invoked by develop during planning. NOT for: reviewing existing plans (use reviewing-impl-plans).

writing-commands

5
from axiomantic/spellbook

Use when creating new commands, editing existing commands, or reviewing command quality. Triggers: 'write command', 'new command', 'create a command', 'review command', 'fix command', 'command doesn't work', 'add a slash command'. NOT for: skill creation (use writing-skills).

verifying-hunches

5
from axiomantic/spellbook

Use when about to claim discovery during debugging. Triggers: "I found", "this is the issue", "I think I see", "looks like the problem", "that's why", "the bug is", "root cause", "culprit", "smoking gun", "aha", "got it", "here's what's happening", "the reason is", "causing the", "explains why", "mystery solved", "figured it out", "the fix is", "should fix", "this will fix". Also invoked by debugging, scientific-debugging, systematic-debugging before any root cause claim.

using-skills

5
from axiomantic/spellbook

System skill loaded at session start to initialize skill routing. Not invoked directly by users. Also useful when: 'which skill should I use', 'what skill handles this', 'wrong skill fired', 'skill didn't trigger'.

using-lsp-tools

5
from axiomantic/spellbook

Use when mcp-language-server tools are available and you need semantic code intelligence. Triggers: 'find definition', 'find references', 'who calls this', 'rename symbol', 'type hierarchy', 'go to definition', 'where is this used', 'where is this defined', 'what type is this'. Provides navigation, refactoring, and type analysis via LSP.

using-git-worktrees

5
from axiomantic/spellbook

Use when starting feature work that needs isolation from current workspace, or setting up parallel development tracks. Triggers: 'worktree', 'separate branch', 'isolate this work', 'don't mess up current work', 'work on two things at once', 'parallel workstreams', 'new branch for this', 'keep my current work safe'.

tooling-discovery

5
from axiomantic/spellbook

Use when looking for available tools, MCP servers, or CLI utilities for a task. Triggers: 'what tools do I have', 'is there an MCP for this', 'what's available', 'find a tool for', 'discover tooling', 'what CLI tools exist'. NOT for: documenting existing tools (use documenting-tools).

testing-strategy

5
from axiomantic/spellbook

Test selection strategy and scope guidance. Triggers: 'which tests should I run', 'test tiers', 'test marks', 'slow tests', 'integration vs unit', 'cross-module regression', 'test scope', 'what should I run', 'select tests', 'test batching'. NOT for: writing tests (use test-driven-development) or fixing broken tests (use fixing-tests).

test-driven-development

5
from axiomantic/spellbook

Use when user explicitly requests test-driven development. Triggers: 'TDD', 'write tests first', 'red green refactor', 'test-first', 'start with the test'. Also invoked by develop and executing-plans for implementation tasks. NOT for: full feature work (use develop, which includes TDD internally).

tarot-mode

5
from axiomantic/spellbook

Use when session returns mode.type='tarot', user says '/tarot', or requests roundtable dialogue with archetypes. Triggers: '/tarot', 'use tarot mode', 'roundtable with archetypes', 'tarot personas'. Session-level mode, not task-level.

smart-reading

5
from axiomantic/spellbook

Behavioral protocol for reading files or command output of unknown size. Loaded automatically for all file reading operations. Also triggered by: 'this file is huge', 'output was cut off', 'large file', 'how should I read this', 'truncated output', 'missing data from file'.