
关于
停止到处抛异常——使用 Either 和 TaskEither 将错误作为值处理,编写更干净、更可预测的代码。
name: fp-errors description: 停止到处抛异常 - 使用 Either 和 TaskEither 将错误作为值处理,编写更清晰、更可预测的代码 risk: unknown source: community version: 1.0.0 author: kadu tags:
- fp-ts
- error-handling
- either
- task-either
- typescript
- validation
- practical
fp-ts 实用错误处理
此技能教你如何在没有 try/catch 意大利面代码的情况下处理错误。没有学术术语——只有解决实际问题的实用模式。
核心思想:错误只是数据。与其将它们抛入虚空并希望有人捕获,不如将它们作为 TypeScript 可以追踪的值返回。
适用场景
- 你需要用
Either或TaskEither替换大量异常的代码。 - 任务涉及验证、领域错误或 TypeScript 中更清晰的错误契约。
- 你想要针对实际应用代码的实用 fp-ts 错误处理指导。
1. 停止到处抛异常
异常的问题
异常在你的类型中是不可见的。它们破坏了函数之间的契约。
// 这个函数签名承诺的:
function getUser(id: string): User
// 它实际做的:
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
}
// 调用者完全不知道这可能失败
const user = getUser(id) // 可能爆炸!
你最终会写出这样的代码:
// 混乱:到处都是 try/catch
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'
// 现在 TypeScript 知道这可能失败
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)
}
// 调用者被强制处理两种情况
const result = getUser(id)
// result 是 Either<string, User> - 错误或成功,永远不会同时
2. 结果模式(Either)
Either<E, A> 很简单:它持有一个错误(E)或一个值(A)。
Left= 错误情况Right= 成功情况(想想 "right" 即 "正确")
import * as E from 'fp-ts/Either'
// 创建值
const success = E.right(42) // Right(42)
const failure = E.left('Oops') // Left('Oops')
// 检查你有什么
if (E.isRight(result)) {
console.log(result.right) // 成功值
} else {
console.log(result.left) // 错误
}
// 更好的方式:用 fold 进行模式匹配
const message = pipe(
result,
E.fold(
(error) => `Failed: ${error}`,
(value) => `Got: ${value}`
)
)
将抛异常的代码转换为 Either
// 用 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: ...)
// 对于会复用的函数,使用 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'
// 转换成功值
const doubled = pipe(
E.right(21),
E.map(n => n * 2)
) // Right(42)
// 转换错误
const betterError = pipe(
E.left('bad'),
E.mapLeft(e => `Error: ${e}`)
) // Left('Error: bad')
// 为错误提供默认值
const value = pipe(
E.left('failed'),
E.getOrElse(() => 0)
) // 0
// 将可空值转换为 Either
const fromNullable = E.fromNullable('not found')
fromNullable(user) // Right(user) 如果存在,Left('not found') 如果为 null/undefined
3. 链接可能失败的操作
真正的威力来自链式调用。每一步都可能失败,但你将其写成一个清晰的管道。
之前:嵌套的 Try/Catch 地狱
// 混乱:每一步都可能失败,到处嵌套 try/catch
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
}
}
兼容工具
Claude CodeCursor
标签
前端开发