Skip to content
Skillpedia
APIs & servers (backend)48k · #3024

fp-async

Lets your agent use patterns for handling asynchronous operations. It helps write cleaner, more organized code, avoiding errors.

What it does

This tool helps organize and manage asynchronous operations (tasks that take time to complete) in your code, especially when they might fail. It uses a method called `TaskEither` to ensure errors are handled clearly and organized, avoiding confusion.

When to use it

  • ✓When you need to handle operations that take time, like fetching data from an API or saving information to a database.
  • ✓If you're struggling to organize error handling in your asynchronous code, with many nested `try/catch` blocks.
  • ✓To create sequences of asynchronous operations that need to run in order and have their errors managed consistently.

Example of what to ask your AI

“How can I use `TaskEither` to fetch data from an API and ensure network errors are handled?”

Want more? Open the sections below.

How to use

The easiest way: copy the message below and paste it into Claude, ChatGPT, Cursor or any AI. It reads the skill and follows the instructions.

Skill link

https://github.com/sickn33/agentic-awesome-skills/blob/main/plugins/agentic-awesome-skills-claude/skills/fp-async/SKILL.md

Ready-to-paste message

Use this skill to help me: https://github.com/sickn33/agentic-awesome-skills/blob/main/plugins/agentic-awesome-skills-claude/skills/fp-async/SKILL.md Read the SKILL.md file at that link and follow its instructions for my task.
How it works

The tool uses `TaskEither`, which is like a box that can hold either a successful result or an error. It allows you to chain asynchronous operations together, ensuring that if one fails, the error is captured and handled cleanly without disrupting the program's flow.

Installation

Easiest way: copy the skill link and paste it into Claude, ChatGPT or Cursor asking it to follow the instructions.

In the terminal (Claude Code, Codex, Cursor), in your project folder:

npx skills add sickn33/agentic-awesome-skills --skill fp-async

Or download just the file:

curl -L https://raw.githubusercontent.com/sickn33/agentic-awesome-skills/main/plugins/agentic-awesome-skills-claude/skills/fp-async/SKILL.md -o SKILL.md
Technical content (original SKILL.md)

Original text by the author, straight from GitHub.

Practical Async Patterns with fp-ts

Stop writing nested try/catch blocks. Stop losing error context. Start building clean async pipelines that handle errors properly.

TaskEither is simply an async operation that tracks success or failure. That's it. No fancy terminology needed.

Detailed Guide

Read the detailed guide before executing this skill. It retains the complete procedure and reference material. Treat its safety, prerequisites, and validation requirements as mandatory. For focused work, load the relevant sections; for end-to-end work, read the guide completely.

When to Use

  • You need async error handling in TypeScript with TaskEither.
  • The task involves wrapping Promises, composing API calls, or replacing nested try/catch flows.
  • You want practical fp-ts async patterns instead of academic explanations.
// TaskEither<Error, User> means:
// "An async operation that either fails with Error or succeeds with User"

5. Real API Examples

Complete Fetch Wrapper

// types.ts
interface ApiError {
  code: string
  message: string
  status: number
  details?: unknown
}

// api.ts
const createApiError = (
  code: string,
  message: string,
  status: number,
  details?: unknown
): ApiError => ({ code, message, status, details })

const request = <T>(
  url: string,
  options: RequestInit = {}
): TE.TaskEither<ApiError, T> =>
  TE.tryCatch(
    async () => {
      const response = await fetch(url, {
        headers: {
          'Content-Type': 'application/json',
          ...options.headers,
        },
        ...options,
      })

      if (!response.ok) {
        const body = await response.json().catch(() => ({}))
        throw createApiError(
          body.code || 'HTTP_ERROR',
          body.message || response.statusText,
          response.status,
          body
        )
      }

      // Handle 204 No Content
      if (response.status === 204) {
        return undefined as T
      }

      return response.json()
    },
    (error): ApiError => {
      if (typeof error === 'object' && error !== null && 'code' in error) {
        return error as ApiError
      }
      return createApiError(
        'NETWORK_ERROR',
        error instanceof Error ? error.message : 'Request failed',
        0
      )
    }
  )

// API client
const api = {
  get: <T>(url: string) => request<T>(url),

  post: <T>(url: string, body: unknown) =>
    request<T>(url, {
      method: 'POST',
      body: JSON.stringify(body)
    }),

  put: <T>(url: string, body: unknown) =>
    request<T>(url, {
      method: 'PUT',
      body: JSON.stringify(body)
    }),

  delete: (url: string) =>
    request<void>(url, { method: 'DELETE' }),
}

// Usage
const getUser = (id: string) => api.get<User>(`/api/users/${id}`)
const createUser = (data: CreateUserDto) => api.post<User>('/api/users', data)
const updateUser = (id: string, data: UpdateUserDto) => api.put<User>(`/api/users/${id}`, data)
const deleteUser = (id: string) => api.delete(`/api/users/${id}`)

Database Operations (Prisma Example)

import { PrismaClient, Prisma } from '@prisma/client'

type DbError =
  | { _tag: 'NotFound'; entity: string; id: string }
  | { _tag: 'UniqueViolation'; field: string }
  | { _tag: 'ConnectionError'; cause: unknown }

const prisma = new PrismaClient()

const wrapPrisma = <T>(
  operation: () => Promise<T>
): TE.TaskEither<DbError, T> =>
  TE.tryCatch(
    operation,
    (error): DbError => {
      if (error instanceof Prisma.PrismaClientKnownRequestError) {
        if (error.code === 'P2002') {
          const field = (error.meta?.target as string[])?.join(', ') || 'unknown'
          return { _tag: 'UniqueViolation', field }
        }
        if (error.code === 'P2025') {
          return { _tag: 'NotFound', entity: 'Record', id: 'unknown' }
        }
      }
      return { _tag: 'ConnectionError', cause: error }
    }
  )

// Repository pattern
const userRepository = {
  findById: (id: string): TE.TaskEither<DbError, User> =>
    pipe(
      wrapPrisma(() => prisma.user.findUnique({ where: { id } })),
      TE.chain(user =>
        user
          ? TE.right(user)
          : TE.left({ _tag: 'NotFound', entity: 'User', id })
      )
    ),

  findByEmail: (email: string): TE.TaskEither<DbError, User | null> =>
    wrapPrisma(() => prisma.user.findUnique({ where: { email } })),

  create: (data: CreateUserInput): TE.TaskEither<DbError, User> =>

…
FAQ

What is `TaskEither`?

`TaskEither` is a way to represent an asynchronous operation that can either succeed or fail. Think of it as a package that contains either the expected result or a description of the error that occurred.

Why use this instead of `try/catch`?

Using `TaskEither` helps avoid many nested `try/catch` blocks, which can make code confusing. It provides a more structured and clear way to handle errors in asynchronous operations.

Do I need to know advanced programming to use this?

Not necessarily. While it uses some functional programming concepts, the main idea is to simplify error handling for operations that take time to complete, making the code more readable.

Details

Original description by the author

Practical async patterns using TaskEither - clean pipelines instead of try/catch hell, with real API examples

repo: sickn33/agentic-awesome-skills

Popularity

GitHub stars
48k
Ranking position
#3024
License
MIT
Updated
10/15/2026

Is this skill useful?

0

Sign in to vote.

Comments

  • …

More skills from this repository

Similar skills