
关于
使用 fp-ts 的 Either 和 TaskEither 将错误作为值处理,实现更清晰、更可预测的 TypeScript 代码。适用于使用 fp-ts 实现错误处理模式。
name: fp-ts-errors description: "使用 fp-ts 的 Either 和 TaskEither 将错误作为值处理,编写更清晰、更可预测的 TypeScript 代码。在使用 fp-ts 实现错误处理模式时使用。" risk: safe source: "https://github.com/whatiskadudoing/fp-ts-skills" date_added: "2026-02-27"
fp-ts 实用错误处理
本技能教你如何在不使用 try/catch 意大利面条代码的情况下处理错误。没有学术术语——只有解决实际问题的实用模式。
何时使用此技能
- 当你想要 TypeScript 中类型安全的错误处理时
- 当用 Either 和 TaskEither 模式替换 try/catch 时
- 当构建需要显式错误类型的 API 或服务时
- 当需要累积多个验证错误时
核心思想:错误只是数据。与其将它们抛入虚空并期望有人捕获,不如将它们作为 TypeScript 可以追踪的值返回。
1. 停止到处抛出异常
异常的问题
异常在你的类型中是不可见的。它们破坏了函数之间的契约。
// What this function signature promises:
function getUser(id: string): User
// What it actually does:
function getUser(id: string): User {
if (!id) throw new Error('ID required')
const user = db.find(id)
if (!user) throw new Error('User not found')
return user
}
// The caller has no idea this can fail
const user = getUser(id) // Might explode!
你最终会得到这样的代码:
// MESSY: try/catch everywhere
function processOrder(orderId: string) {
let order
try {
order = getOrder(orderId)
} catch (e) {
console.error('Failed to get order')
return null
}
let user
try {
user = getUser(order.userId)
} catch (e) {
console.error('Failed to get user')
return null
}
let payment
try {
payment = chargeCard(user.cardId, order.total)
} catch (e) {
console.error('Payment failed')
return null
}
return { order, user, payment }
}
解决方案:将错误作为值返回
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'
// Now TypeScript KNOWS this can fail
function getUser(id: string): E.Either<string, User> {
if (!id) return E.left('ID required')
const user = db.find(id)
if (!user) return E.left('User not found')
return E.right(user)
}
// The caller is forced to handle both cases
const result = getUser(id)
// result is Either<string, User> - error OR success, never both
2. 结果模式(Either)
Either<E, A> 很简单:它持有一个错误(E)或一个值(A)。
Left= 错误情况Right= 成功情况(可以理解为"正确")
import * as E from 'fp-ts/Either'
// Creating values
const success = E.right(42) // Right(42)
const failure = E.left('Oops') // Left('Oops')
// Checking what you have
if (E.isRight(result)) {
console.log(result.right) // The success value
} else {
console.log(result.left) // The error
}
// Better: pattern match with fold
const message = pipe(
result,
E.fold(
(error) => `Failed: ${error}`,
(value) => `Got: ${value}`
)
)
将抛出异常的代码转换为 Either
// Wrap any throwing function with tryCatch
const parseJSON = (json: string): E.Either<Error, unknown> =>
E.tryCatch(
() => JSON.parse(json),
(e) => (e instanceof Error ? e : new Error(String(e)))
)
parseJSON('{"valid": true}') // Right({ valid: true })
parseJSON('not json') // Left(SyntaxError: ...)
// For functions you'll reuse, use tryCatchK
const safeParseJSON = E.tryCatchK(
JSON.parse,
(e) => (e instanceof Error ? e : new Error(String(e)))
)
常用 Either 操作
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'
// Transform the success value
const doubled = pipe(
E.right(21),
E.map(n => n * 2)
) // Right(42)
// Transform the error
const betterError = pipe(
E.left('bad'),
E.mapLeft(e => `Error: ${e}`)
) // Left('Error: bad')
// Provide a default for errors
const value = pipe(
E.left('failed'),
E.getOrElse(() => 0)
) // 0
// Convert nullable to Either
const fromNullable = E.fromNullable('not found')
fromNullable(user) // Right(user) if exists, Left('not found') if null/undefined
3. 链式调用可能失败的操作
真正的威力来自链式调用。每一步都可能失败,但你可以将其写成一个清晰的管道。
之前:嵌套的 Try/Catch 地狱
// MESSY: Each step can fail, nested try/catch everywhere
function processUserOrder(userId: string, productId: string): Result | null {
let user
try {
user = getUser(userId)
} catch (e) {
logError('User fetch failed', e)
return null
}
if (!user.isActive) {
logError('User not active')
return null
}
let product
try {
product = getProduct(productId)
} catch (e) {
logError('Product fetch failed', e)
return null
}
if (product.stock
兼容工具
Claude CodeCursor
标签
前端开发