Token导航 LogoToken导航

Agent Skill · 前端设计

fp-ts-task-either(fp ts 任务)

fp-ts-task-either 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

fp-ts-task-either 是前端设计类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描有提醒项,按安装量在前端设计类的 10,216 个 Skill 里排第 4,614。

总安装 840周安装 35GitHub Stars 6下载 280核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:fp-ts-task-either(fp ts 任务)
来源仓库:https://github.com/whatiskadudoing/fp-ts-skills
仓库路径:skills/fp-ts-task-either
安装命令:
npx skills add https://github.com/whatiskadudoing/fp-ts-skills --skill fp-ts-task-either
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/whatiskadudoing/fp-ts-skills --skill fp-ts-task-either

这个 Skill 是做什么的

前端设计类 Skill 关注页面结构、视觉规范和组件写法,输出通常是可运行的界面代码。

fp-ts-task-either 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息。适合在 Codex、Claude、Cursor、Gemini CLI 中围绕仓库状态和协作事项进行整理。可通过 npx skills add 命令从指定仓库安装并使用。

安装前需确认权限范围、维护状态及是否触发联网或文件操作。建议结合原始 README 核验具体用法和功能边界。

来源仓库内路径:skills/fp-ts-task-either

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/whatiskadudoing/fp-ts-skills --skill fp-ts-tas…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

fp-ts-task-either 怎么安装?

在终端执行 npx skills add https://github.com/whatiskadudoing/fp-ts-skills --skill fp-ts-task-either。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

fp-ts-task-either 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

fp-ts-task-either 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

fp-ts-task-either 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

fp-ts-task-either 用的人多吗?

公开数据是累计安装 840 次,近一周安装 35 次,下载 280 次,GitHub 6 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

fp-ts-task-either 安全吗?

第三方扫描有提醒项,说明存在需要人工确认的行为。安装前请打开源码看清它读了什么、联网到哪里。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方扫描存在提醒项或未通过项,需要人工逐条确认。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理前端设计类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

前端设计类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
frontend-design

前端设计 · Codex、Claude

8,823,360126,072只读MIT通过
vercel-composition-patterns

Vercel 组合模式 · Codex、Claude

3,858,81625,893需要联网MIT通过
vercel-react-native-skills

Vercel React native skills 前端 · Codex、Claude

2,468,66425,878只读MIT通过
adapt

adapt 搜索 · Codex、Claude

2,027,04023,125操作浏览器未明确通过
bolder

bolder 分析 · Codex、Claude

2,004,28823,130只读未明确通过
fp-ts-task-either

fp ts 任务 · 本页

8406需要联网未明确有提醒

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

fp-ts TaskEither Async Patterns

TaskEither combines the laziness of Task with the error handling of Either, providing a powerful abstraction for async operations that can fail.

Core Concepts

import * as TE from 'fp-ts/TaskEither'
import * as T from 'fp-ts/Task'
import * as E from 'fp-ts/Either'
import { pipe, flow } from 'fp-ts/function'

TaskEither<E, A> is equivalent to () => Promise<Either<E, A>>

  • E = Error type (left)
  • A = Success type (right)
  • Lazy: nothing executes until you call the function
  • Composable: chain operations without try/catch

1. Converting Promises to TaskEither

Using tryCatch

The primary way to lift Promises into TaskEither:

import * as TE from 'fp-ts/TaskEither'

// Basic tryCatch pattern
const fetchUser = (id: string): TE.TaskEither<Error, User> =>
  TE.tryCatch(
    () => fetch(`/api/users/${id}`).then(res => res.json()),
    (reason) => new Error(String(reason))
  )

// With typed errors
interface ApiError {
  code: string
  message: string
  status: number
}

const fetchUserTyped = (id: string): TE.TaskEither<ApiError, User> =>
  TE.tryCatch(
    () => fetch(`/api/users/${id}`).then(res => res.json()),
    (reason): ApiError => ({
      code: 'FETCH_ERROR',
      message: reason instanceof Error ? reason.message : 'Unknown error',
      status: 500
    })
  )

From existing Either

// Lift an Either into TaskEither
const fromEither: TE.TaskEither<Error, number> = TE.fromEither(E.right(42))

// From a nullable value
const fromNullable = TE.fromNullable(new Error('Value was null'))
const result = fromNullable(maybeValue) // TaskEither<Error, NonNullable<T>>

// From an Option
import * as O from 'fp-ts/Option'
const fromOption = TE.fromOption(() => new Error('None value'))
const optionResult = fromOption(O.some(42)) // TaskEither<Error, number>

Creating TaskEither values directly

// Success value
const success = TE.right<Error, number>(42)

// Error value
const failure = TE.left<Error, number>(new Error('Something failed'))

// From a predicate
const validatePositive = TE.fromPredicate(
  (n: number) => n > 0,
  (n) => new Error(`Expected positive, got ${n}`)
)

2. Handling Async Errors Functionally

Mapping over errors

// Transform the error type
const withMappedError = pipe(
  fetchUser('123'),
  TE.mapLeft((error) => ({
    type: 'USER_FETCH_ERROR' as const,
    originalError: error,
    timestamp: Date.now()
  }))
)

// Bifunctor: map both sides
const mapped = pipe(
  fetchUser('123'),
  TE.bimap(
    (error) => new DetailedError(error),  // map error
    (user) => user.profile                 // map success
  )
)

Error filtering

// Filter with error on false
const validateAge = pipe(
  fetchUser('123'),
  TE.filterOrElse(
    (user) => user.age >= 18,
    (user) => new Error(`User ${user.name} is underage`)
  )
)

3. Chaining Async Operations

Sequential chaining with chain/flatMap

interface User { id: string; name: string; teamId: string }
interface Team { id: string; name: string; orgId: string }
interface Org { id: string; name: string }

const fetchUser = (id: string): TE.TaskEither<Error, User> =>
  TE.tryCatch(() => api.getUser(id), toError)

const fetchTeam = (teamId: string): TE.TaskEither<Error, Team> =>
  TE.tryCatch(() => api.getTeam(teamId), toError)

const fetchOrg = (orgId: string): TE.TaskEither<Error, Org> =>
  TE.tryCatch(() => api.getOrg(orgId), toError)

// Chain operations sequentially
const getUserOrg = (userId: string): TE.TaskEither<Error, Org> =>
  pipe(
    fetchUser(userId),
    TE.chain((user) => fetchTeam(user.teamId)),
    TE.chain((team) => fetchOrg(team.orgId))
  )

// flatMap is an alias for chain
const getUserOrgAlt = (userId: string): TE.TaskEither<Error, Org> =>
  pipe(
    fetchUser(userId),
    TE.flatMap((user) => fetchTeam(user.teamId)),
    TE.flatMap((team) => fetchOrg(team.orgId))
  )

Chaining with intermediate values

// Use bind to accumulate values
const getFullContext = (userId: string) =>
  pipe(
    TE.Do,
    TE.bind('user', () => fetchUser(userId)),
    TE.bind('team', ({ user }) => fetchTeam(user.teamId)),
    TE.bind('org', ({ team }) => fetchOrg(team.orgId)),
    TE.map(({ user, team, org }) => ({
      userName: user.name,
      teamName: team.name,
      orgName: org.name
    }))
  )

4. Parallel vs Sequential Execution

Parallel execution with sequenceArray

import * as A from 'fp-ts/Array'

const userIds = ['1', '2', '3', '4', '5']

// Parallel: all requests start immediately
// Fails fast: returns first error encountered
const fetchAllUsersParallel = pipe(
  userIds.map(fetchUser),
  TE.sequenceArray  // TaskEither<Error, readonly User[]>
)

// Sequential: one at a time (use when order matters or rate limiting)
const fetchAllUsersSequential = pipe(
  userIds,
  A.traverse(TE.ApplicativeSeq)(fetchUser)
)

Parallel with traverseArray

// More idiomatic: traverse combines map + sequence
const fetchAllUsers = (ids: string[]): TE.TaskEither<Error, readonly User[]> =>
  pipe(ids, TE.traverseArray(fetchUser))

// With index
const fetchWithIndex = pipe(
  userIds,
  TE.traverseArrayWithIndex((index, id) =>
    pipe(
      fetchUser(id),
      TE.map(user => ({ ...user, index }))
    )
  )
)

Collecting all errors vs fail fast

import * as These from 'fp-ts/These'
import * as TH from 'fp-ts/TaskThese'

// For collecting all errors, consider TaskThese
// Or use validation with sequenceT

import { sequenceT } from 'fp-ts/Apply'

// Parallel execution, collects results
const parallel = sequenceT(TE.ApplyPar)(
  fetchUser('1'),
  fetchTeam('team-1'),
  fetchOrg('org-1')
) // TaskEither<Error, [User, Team, Org]>

// Sequential execution
const sequential = sequenceT(TE.ApplySeq)(
  fetchUser('1'),
  fetchTeam('team-1'),
  fetchOrg('org-1')
)

Concurrent with limit

// For controlled concurrency, batch your operations
const batchSize = 3

const fetchInBatches = (ids: string[]): TE.TaskEither<Error, User[]> => {
  const batches = chunk(ids, batchSize)

  return pipe(
    batches,
    A.traverse(TE.ApplicativeSeq)((batch) =>
      pipe(batch, TE.traverseArray(fetchUser))
    ),
    TE.map(A.flatten)
  )
}

5. Error Recovery with orElse

Basic error recovery

// Try primary, fall back to secondary
const fetchWithFallback = pipe(
  fetchFromPrimaryApi(id),
  TE.orElse((primaryError) =>
    pipe(
      fetchFromBackupApi(id),
      TE.mapLeft((backupError) => ({
        primary: primaryError,
        backup: backupError
      }))
    )
  )
)

// Recover to a default value
const fetchWithDefault = pipe(
  fetchUser(id),
  TE.orElse(() => TE.right(defaultUser))
)

// orElseW when recovery has different error type
const fetchWithTypedFallback = pipe(
  fetchFromApi(id),           // TaskEither<ApiError, User>
  TE.orElseW((apiError) =>    // orElseW allows different error type
    fetchFromCache(id)         // TaskEither<CacheError, User>
  )
) // TaskEither<CacheError, User>

Retry patterns

const retry = <E, A>(
  te: TE.TaskEither<E, A>,
  retries: number,
  delay: number
): TE.TaskEither<E, A> =>
  pipe(
    te,
    TE.orElse((error) =>
      retries > 0
        ? pipe(
            T.delay(delay)(T.of(undefined)),
            T.chain(() => retry(te, retries - 1, delay * 2))
          )
        : TE.left(error)
    )
  )

// Usage
const fetchWithRetry = retry(fetchUser('123'), 3, 1000)

Conditional recovery

// Only recover from specific errors
const recoverFromNotFound = pipe(
  fetchUser(id),
  TE.orElse((error) =>
    error.code === 'NOT_FOUND'
      ? TE.right(createDefaultUser(id))
      : TE.left(error)  // re-throw other errors
  )
)

// Alt: try alternatives in order
import { alt } from 'fp-ts/TaskEither'

const fetchFromAnywhere = pipe(
  fetchFromCache(id),
  TE.alt(() => fetchFromApi(id)),
  TE.alt(() => fetchFromBackup(id))
)

6. Pattern Matching Async Results

Using fold/match

// fold executes the TaskEither and handles both cases
const handleResult = pipe(
  fetchUser('123'),
  TE.fold(
    (error) => T.of(`Error: ${error.message}`),
    (user) => T.of(`Welcome, ${user.name}`)
  )
) // Task<string> - no longer has error channel

// match is an alias for fold
const handleWithMatch = pipe(
  fetchUser('123'),
  TE.match(
    (error) => ({ success: false, error }),
    (user) => ({ success: true, data: user })
  )
)

// matchW when handlers return different types
const handleWithMatchW = pipe(
  fetchUser('123'),
  TE.matchW(
    (error) => ({ type: 'error' as const, error }),
    (user) => ({ type: 'success' as const, user })
  )
)

Getting the underlying Either

// Execute and get the Either
const getEither = async () => {
  const either = await fetchUser('123')()

  if (E.isLeft(either)) {
    console.error('Failed:', either.left)
  } else {
    console.log('User:', either.right)
  }
}

// Using getOrElse for default
const getWithDefault = pipe(
  fetchUser('123'),
  TE.getOrElse((error) => T.of(defaultUser))
) // Task<User>

// getOrElseW when default has different type
const getOrNull = pipe(
  fetchUser('123'),
  TE.getOrElseW(() => T.of(null))
) // Task<User | null>

7. Do Notation for Complex Workflows

Building complex operations

interface OrderContext {
  user: User
  cart: Cart
  payment: PaymentMethod
  shipping: ShippingAddress
}

const processOrder = (
  userId: string,
  cartId: string
): TE.TaskEither<OrderError, OrderConfirmation> =>
  pipe(
    TE.Do,
    // Bind values sequentially
    TE.bind('user', () => fetchUser(userId)),
    TE.bind('cart', () => fetchCart(cartId)),

    // Validate intermediate results
    TE.filterOrElse(
      ({ cart }) => cart.items.length > 0,
      () => ({ code: 'EMPTY_CART', message: 'Cart is empty' })
    ),

    // Continue building context
    TE.bind('payment', ({ user }) => getDefaultPayment(user.id)),
    TE.bind('shipping', ({ user }) => getDefaultShipping(user.id)),

    // Calculate derived values
    TE.bind('total', ({ cart }) => TE.right(calculateTotal(cart))),

    // Validate before final operation
    TE.filterOrElse(
      ({ payment, total }) => payment.limit >= total,
      ({ total }) => ({ code: 'LIMIT_EXCEEDED', message: `Order total ${total} exceeds limit` })
    ),

    // Final operation
    TE.chain(({ user, cart, payment, shipping, total }) =>
      createOrder({ user, cart, payment, shipping, total })
    )
  )

Parallel fetching within Do

const getOrderDetails = (orderId: string) =>
  pipe(
    TE.Do,
    TE.bind('order', () => fetchOrder(orderId)),

    // Parallel fetch based on order data
    TE.bind('details', ({ order }) =>
      sequenceT(TE.ApplyPar)(
        fetchUser(order.userId),
        fetchProducts(order.productIds),
        fetchShipping(order.shippingId)
      )
    ),

    TE.map(({ order, details: [user, products, shipping] }) => ({
      order,
      user,
      products,
      shipping
    }))
  )

Using apS for simpler additions

// apS is like bind but doesn't depend on previous values
const enrichUser = (userId: string) =>
  pipe(
    fetchUser(userId),
    TE.bindTo('user'),  // Wrap in { user: ... }
    TE.apS('config', fetchAppConfig()),  // Add independent value
    TE.apS('features', fetchFeatureFlags()),
    TE.bind('preferences', ({ user }) => fetchPreferences(user.id))  // Dependent
  )

8. Real-World API Call Patterns

Typed API client

interface ApiConfig {
  baseUrl: string
  timeout: number
}

interface ApiError {
  code: string
  message: string
  status: number
  details?: unknown
}

const createApiClient = (config: ApiConfig) => {
  const request = <T>(
    method: string,
    path: string,
    body?: unknown
  ): TE.TaskEither<ApiError, T> =>
    TE.tryCatch(
      async () => {
        const response = await fetch(`${config.baseUrl}${path}`, {
          method,
          headers: { 'Content-Type': 'application/json' },
          body: body ? JSON.stringify(body) : undefined,
          signal: AbortSignal.timeout(config.timeout)
        })

        if (!response.ok) {
          const error = await response.json().catch(() => ({}))
          throw { status: response.status, ...error }
        }

        return response.json()
      },
      (error): ApiError => ({
        code: 'API_ERROR',
        message: error instanceof Error ? error.message : 'Request failed',
        status: (error as any)?.status ?? 500,
        details: error
      })
    )

  return {
    get: <T>(path: string) => request<T>('GET', path),
    post: <T>(path: string, body: unknown) => request<T>('POST', path, body),
    put: <T>(path: string, body: unknown) => request<T>('PUT', path, body),
    delete: <T>(path: string) => request<T>('DELETE', path)
  }
}

// Usage
const api = createApiClient({ baseUrl: '/api', timeout: 5000 })

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

Request with validation

import * as t from 'io-ts'
import { PathReporter } from 'io-ts/PathReporter'

const UserCodec = t.type({
  id: t.string,
  name: t.string,
  email: t.string
})

type User = t.TypeOf<typeof UserCodec>

const fetchAndValidate = <A>(
  codec: t.Type<A>,
  url: string
): TE.TaskEither<Error, A> =>
  pipe(
    TE.tryCatch(
      () => fetch(url).then(r => r.json()),
      (e) => new Error(`Fetch failed: ${e}`)
    ),
    TE.chainEitherK((data) =>
      pipe(
        codec.decode(data),
        E.mapLeft((errors) =>
          new Error(`Validation failed: ${PathReporter.report(E.left(errors)).join(', ')}`)
        )
      )
    )
  )

const getValidatedUser = (id: string) =>
  fetchAndValidate(UserCodec, `/api/users/${id}`)

9. Database Operation Patterns

Repository pattern

interface Repository<E, T, ID> {
  findById: (id: ID) => TE.TaskEither<E, T>
  findAll: () => TE.TaskEither<E, readonly T[]>
  save: (entity: T) => TE.TaskEither<E, T>
  delete: (id: ID) => TE.TaskEither<E, void>
}

interface DbError {
  code: 'NOT_FOUND' | 'DUPLICATE' | 'CONSTRAINT' | 'CONNECTION'
  message: string
  cause?: unknown
}

const createUserRepository = (db: Database): Repository<DbError, User, string> => ({
  findById: (id) =>
    pipe(
      TE.tryCatch(
        () => db.query('SELECT * FROM users WHERE id = ?', [id]),
        (e): DbError => ({ code: 'CONNECTION', message: String(e), cause: e })
      ),
      TE.chain((rows) =>
        rows.length === 0
          ? TE.left({ code: 'NOT_FOUND', message: `User ${id} not found` })
          : TE.right(rows[0] as User)
      )
    ),

  findAll: () =>
    TE.tryCatch(
      () => db.query('SELECT * FROM users'),
      (e): DbError => ({ code: 'CONNECTION', message: String(e), cause: e })
    ),

  save: (user) =>
    TE.tryCatch(
      () => db.query(
        'INSERT INTO users (id, name, email) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET name = ?, email = ?',
        [user.id, user.name, user.email, user.name, user.email]
      ).then(() => user),
      (e): DbError => {
        if (String(e).includes('UNIQUE constraint')) {
          return { code: 'DUPLICATE', message: 'Email already exists', cause: e }
        }
        return { code: 'CONNECTION', message: String(e), cause: e }
      }
    ),

  delete: (id) =>
    TE.tryCatch(
      () => db.query('DELETE FROM users WHERE id = ?', [id]).then(() => undefined),
      (e): DbError => ({ code: 'CONNECTION', message: String(e), cause: e })
    )
})

Transaction handling

interface Transaction {
  query: (sql: string, params?: unknown[]) => Promise<unknown>
  commit: () => Promise<void>
  rollback: () => Promise<void>
}

const withTransaction = <E, A>(
  db: Database,
  operation: (tx: Transaction) => TE.TaskEither<E, A>
): TE.TaskEither<E | DbError, A> =>
  pipe(
    TE.tryCatch(
      () => db.beginTransaction(),
      (e): DbError => ({ code: 'CONNECTION', message: 'Failed to start transaction', cause: e })
    ),
    TE.chain((tx) =>
      pipe(
        operation(tx),
        TE.chainFirst(() =>
          TE.tryCatch(
            () => tx.commit(),
            (e): DbError => ({ code: 'CONNECTION', message: 'Commit failed', cause: e })
          )
        ),
        TE.orElse((error) =>
          pipe(
            TE.tryCatch(() => tx.rollback(), () => error),
            TE.chain(() => TE.left(error))
          )
        )
      )
    )
  )

// Usage
const transferFunds = (fromId: string, toId: string, amount: number) =>
  withTransaction(db, (tx) =>
    pipe(
      TE.Do,
      TE.bind('from', () => getAccount(tx, fromId)),
      TE.bind('to', () => getAccount(tx, toId)),
      TE.filterOrElse(
        ({ from }) => from.balance >= amount,
        () => ({ code: 'INSUFFICIENT_FUNDS', message: 'Not enough balance' })
      ),
      TE.chain(({ from, to }) =>
        sequenceT(TE.ApplySeq)(
          updateBalance(tx, fromId, from.balance - amount),
          updateBalance(tx, toId, to.balance + amount)
        )
      ),
      TE.map(() => ({ success: true, amount }))
    )
  )

10. Task vs TaskEither: When to Use Which

Use Task when:

import * as T from 'fp-ts/Task'

// 1. Operation cannot fail
const delay = (ms: number): T.Task<void> =>
  () => new Promise(resolve => setTimeout(resolve, ms))

// 2. Errors are handled elsewhere
const logMessage = (msg: string): T.Task<void> =>
  () => console.log(msg) as unknown as Promise<void>

// 3. You want to ignore errors
const fetchOrDefault = (url: string, defaultValue: Data): T.Task<Data> =>
  pipe(
    TE.tryCatch(() => fetch(url).then(r => r.json()), E.toError),
    TE.getOrElse(() => T.of(defaultValue))
  )

// 4. Fire and forget
const trackAnalytics = (event: Event): T.Task<void> =>
  () => analytics.track(event).catch(() => {}) // Errors swallowed

Use TaskEither when:

// 1. Operation can fail and you need to handle the error
const fetchUser = (id: string): TE.TaskEither<Error, User> =>
  TE.tryCatch(() => api.getUser(id), E.toError)

// 2. You need typed errors for different failure modes
type AuthError =
  | { type: 'INVALID_CREDENTIALS' }
  | { type: 'EXPIRED_TOKEN' }
  | { type: 'NETWORK_ERROR'; cause: Error }

const authenticate = (token: string): TE.TaskEither<AuthError, User> => { /* ... */ }

// 3. Error recovery is part of business logic
const getConfig = (): TE.TaskEither<ConfigError, Config> =>
  pipe(
    fetchRemoteConfig(),
    TE.orElse(() => loadLocalConfig()),
    TE.orElse(() => TE.right(defaultConfig))
  )

// 4. Composing multiple fallible operations
const processOrder = (orderId: string): TE.TaskEither<OrderError, Receipt> =>
  pipe(
    validateOrder(orderId),
    TE.chain(chargePayment),
    TE.chain(fulfillOrder),
    TE.chain(sendConfirmation)
  )

Converting between them

// Task to TaskEither (infallible to fallible)
const taskToTE = <A>(task: T.Task<A>): TE.TaskEither<never, A> =>
  pipe(task, T.map(E.right))

// TaskEither to Task (handle/ignore error)
const teToTask = <E, A>(te: TE.TaskEither<E, A>, defaultValue: A): T.Task<A> =>
  TE.getOrElse(() => T.of(defaultValue))(te)

// TaskEither to Task (throw on error - escape hatch)
const teToTaskThrow = <E, A>(te: TE.TaskEither<E, A>): T.Task<A> =>
  pipe(
    te,
    TE.getOrElse((e) => () => Promise.reject(e))
  )

Quick Reference

OperationFunctionDescription
Create successTE.right(value)Wrap value in Right
Create failureTE.left(error)Wrap error in Left
From PromiseTE.tryCatch(promise, onError)Convert Promise to TE
Transform valueTE.map(f)Apply f to success value
Transform errorTE.mapLeft(f)Apply f to error value
Chain operationsTE.chain(f) / TE.flatMap(f)Sequence dependent operations
Recover from errorTE.orElse(f)Try alternative on error
Handle both casesTE.fold(onError, onSuccess)Pattern match result
Parallel arrayTE.traverseArray(f)Map + sequence in parallel
Sequential arrayA.traverse(TE.ApplicativeSeq)(f)Map + sequence in order
Filter with errorTE.filterOrElse(pred, onFalse)Validate with error
Get or defaultTE.getOrElse(onError)Extract value with fallback

Common Patterns Summary

// 1. Fetch with error handling
const fetch = TE.tryCatch(() => api.get(url), toError)

// 2. Chain dependent calls
pipe(getA(), TE.chain(a => getB(a.id)), TE.chain(b => getC(b.id)))

// 3. Parallel independent calls
sequenceT(TE.ApplyPar)(getA(), getB(), getC())

// 4. Build context with Do
pipe(TE.Do, TE.bind('a', () => getA()), TE.bind('b', ({a}) => getB(a)))

// 5. Recover from errors
pipe(primary(), TE.orElse(() => fallback()))

// 6. Execute and handle result
pipe(operation(), TE.fold(handleError, handleSuccess))()

平台分布

Codex

37.65%
按下载量换算105

Claude

27.91%
按下载量换算78

Cursor

18.87%
按下载量换算53

Gemini CLI

10.64%
按下载量换算30

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在提醒或未通过项,安装前请自行复核。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills