🔥错误传播:如何让 Maybe 或 Either 链式处理失败?
——避免层层 if (result) return ...
“不要用 if 检查错误,
而要用类型系统让错误自动传播。”
一、问题:命令式错误处理的噩梦
传统写法:嵌套 if 检查
js
function getUserPreferences(userId) {
const user = getUserById(userId);
if (!user) return null;
const profile = getProfile(user.profileId);
if (!profile) return null;
const prefs = getPreferences(profile.prefId);
if (!prefs) return null;
return enrichPreferences(prefs);
}- 重复模板代码
null含义模糊(未找到?错误?)- 难以区分“正常空值”和“错误”
- 无法携带错误信息
二、解决方案:Either 和 Maybe 类型
核心思想
用类型表示“可能失败”的计算,
并通过 Monad 的 chain(flatMap) 自动传播错误。
三、Maybe:处理“存在性”问题
Maybe 是什么?
一个容器,表示值可能存在(Just)或不存在(Nothing)。
js
// 签名:
// getUserById :: Id -> Maybe User
// getProfile :: ProfileId -> Maybe Profile
// getPrefs :: PrefId -> Maybe Preferences链式调用(自动传播)
js
const getUserPreferences = pipe(
getUserById, // Maybe User
chain(user => getProfile(user.profileId)), // Maybe Profile
chain(profile => getPreferences(profile.prefId)), // Maybe Preferences
map(enrichPreferences) // Maybe EnrichedPrefs
);chain:仅在Just时执行下一步map:转换值,失败则跳过- 无需
if,错误自动短路
Maybe 实现(简化版)
js
const Maybe = {
of: value => Just(value),
nothing: () => ({
isNothing: true,
map: () => Maybe.nothing(),
chain: () => Maybe.nothing()
})
};
const Just = value => ({
isNothing: false,
map: f => Just(f(value)),
chain: f => f(value) // f 返回 Maybe
});四、Either:处理“错误信息”问题
Either 是什么?
表示两种可能:Right(成功)或 Left(失败,含错误信息)。
js
// 签名:
// getUserById :: Id -> Either Error User
// getProfile :: ProfileId -> Either Error Profile链式调用(携带错误信息)
js
const getUserPreferences = pipe(
getUserById,
chain(user => user.active
? Either.of(user)
: Left('User inactive')
),
chain(user => getProfile(user.profileId)),
chain(profile => getPreferences(profile.prefId)),
map(enrichPreferences)
);
// 调用
const result = getUserPreferences(123);
if (result.isRight) {
console.log(result.value);
} else {
console.error(result.error); // "User inactive"
}- 失败时携带
Error对象 - 可用于日志、用户提示
- 依然是链式,无
if
Either 实现(简化版)
js
const Right = value => ({
isRight: true,
map: f => Right(f(value)),
chain: f => f(value)
});
const Left = error => ({
isRight: false,
map: () => Left(error),
chain: () => Left(error)
});
const Either = {
of: Right,
left: Left
};五、真实场景对比
命令式:脆弱且冗长
js
function processOrder(orderId) {
const order = db.findOrder(orderId);
if (!order) return { error: 'Order not found' };
if (order.status !== 'pending') {
return { error: 'Order not pending' };
}
const user = db.findUser(order.userId);
if (!user) return { error: 'User not found' };
const result = chargeUser(user, order.amount);
if (!result.success) {
return { error: result.message };
}
return { data: result };
}函数式:清晰且健壮
js
const processOrder = pipe(
db.findOrder, // Either NotFound Order
chain(order =>
order.status === 'pending'
? Either.of(order)
: Left('Order not pending')
),
chain(order =>
db.findUser(order.userId) // Either NotFound User
),
chain(user =>
chargeUser(user, order.amount) // Either ChargeError Result
)
);- 所有步骤统一用
Either - 错误自动传播
- 类型安全(在 TS 中)
六、工程实践建议
1. 在边界处转换
- API 调用、数据库查询 → 返回
Either - UI 层 → 解构
Either,显示成功/错误
js
api.getUser(123).then(result => {
if (result.isRight) {
showUser(result.value);
} else {
showError(result.error.message);
}
});2. 用 fp-ts 或 crocks
ts
import * as E from 'fp-ts/Either';
import { pipe } from 'fp-ts/function';
const result = pipe(
E.tryCatch(
() => unsafeApiCall(),
() => new Error('API failed')
),
E.chain(validateResponse)
);3. 避免过度使用
- 简单逻辑无需
Either - 不要为了 FP 而 FP
七、总结:错误处理的范式转变
| 方式 | 特点 |
|---|---|
命令式 if 检查 | 手动传播,易遗漏,重复 |
异常 try/catch | 非表达式,难以组合,可能遗漏 |
Maybe/Either | 自动传播,类型安全,可组合 |
“让类型系统替你写 if。”
当你使用 Either 链:
- 你不再“处理”错误,
- 你定义“成功路径”,
- 错误自动消失在
Left中。
这才是优雅的错误传播。