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.

Stars 5
Forks 2

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

  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

  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.

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

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;
  }
}
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;
}
typescript
// BAD - SyntaxError
function loadUser() {
  const user = await database.getUser();
  return user;
}

// CORRECT
async function loadUser() {
  const user = await database.getUser();
  return user;
}
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');
  }
}
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;
  }
}

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

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

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

Expand your agent's capabilities with these related and highly-rated skills.

axiomantic/spellbook

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.

5 2
Explore
axiomantic/spellbook

documentation-updates

Use after modifying library skills, library commands, or agents to ensure CHANGELOG, README, and docs are updated

5 2
Explore
axiomantic/spellbook

project-encyclopedia

[DEPRECATED] Use project-level AGENTS.md files instead. Previously used for first-session codebase onboarding and persistent glossary creation.

5 2
Explore
axiomantic/spellbook

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

5 2
Explore
axiomantic/spellbook

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.

5 2
Explore
axiomantic/spellbook

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.

5 2
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results