前端开发中除网络I/O外,何时使用fp-ts的Either?
在前端开发中使用fp-ts Either的非网络I/O场景
Either的核心价值是显式区分成功与失败分支,用类型系统约束错误处理,避免隐式的异常或空值问题。除了网络请求,前端还有很多场景适合用它:
1. 数据解析与转换
前端经常需要处理非可靠的字符串数据,解析失败是常见情况,用Either可以把错误捕获并明确标记:
- JSON解析:处理后端非标准JSON、本地存储的JSON字符串时,替代
try/catch让错误处理更优雅:
import { Either, tryCatch } from 'fp-ts/Either'; const parseJSON = <T>(str: string): Either<Error, T> => tryCatch( () => JSON.parse(str), (err) => err as Error );
- 格式转换:比如把字符串转数字、日期,转换失败时返回特定错误:
import { Either, left, right } from 'fp-ts/Either'; const toNumber = (str: string): Either<'invalid_number', number> => { const num = Number(str); return isNaN(num) ? left('invalid_number') : right(num); };
2. 表单验证
复杂表单的多字段校验场景,用Either可以清晰返回字段级错误,结合fp-ts的sequenceT或Validation还能实现并行校验:
import { Either, left, right } from 'fp-ts/Either'; type FormError = { field: string; message: string }; const validateEmail = (email: string): Either<FormError, string> => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) ? right(email) : left({ field: 'email', message: '邮箱格式不符合要求' }); const validatePassword = (pwd: string): Either<FormError, string> => pwd.length >= 6 && /[A-Z]/.test(pwd) ? right(pwd) : left({ field: 'password', message: '密码需至少6位且包含大写字母' });
3. 本地存储操作
LocalStorage/SessionStorage的读写存在多种失败可能:数据不存在、格式错误、存储空间不足,用Either统一处理这些异常:
import { Either, left, right } from 'fp-ts/Either'; import { parseJSON } from './utils'; // 复用之前的JSON解析函数 type UserPrefs = { theme: 'light' | 'dark'; notifications: boolean }; const getUserPrefs = (): Either<string, UserPrefs> => { const raw = localStorage.getItem('userPrefs'); if (!raw) return left('未找到用户偏好设置'); const parsed = parseJSON<UserPrefs>(raw); return parsed.fold( () => left('用户偏好设置格式损坏'), right ); };
4. 业务状态计算
从多个状态片段推导业务结果时,任何一个环节不符合规则都需要返回错误,Either能让错误分支更明确:
import { Either, left, right } from 'fp-ts/Either'; type CartItem = { id: string; quantity: number; price: string }; type CartError = 'invalid_price' | 'insufficient_stock'; const calculateCartTotal = (items: CartItem[], stock: Record<string, number>): Either<CartError, number> => { let total = 0; for (const item of items) { const price = parseFloat(item.price); if (isNaN(price)) return left('invalid_price'); if (stock[item.id] < item.quantity) return left('insufficient_stock'); total += price * item.quantity; } return right(total); };
5. 前端文件处理
用户上传文件的读取、转换操作(比如FileReader读文本/Base64)可能失败,结合TaskEither处理异步场景:
import { TaskEither, tryCatch as tryCatchTask } from 'fp-ts/TaskEither'; const readFileAsText = (file: File): TaskEither<Error, string> => tryCatchTask( () => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result as string); reader.onerror = () => reject(reader.error); reader.readAsText(file); }), (err) => err as Error );
总结来说,只要是存在明确成功/失败分支,且需要显式处理错误的场景,都可以用Either替代传统的try/catch、返回null/undefined或抛出异常的方式,让代码的类型更安全,错误逻辑更清晰。
内容的提问来源于stack exchange,提问作者LKB
相关产品推荐
相关产品推荐

