neverthrow,ts 中优雅的异常处理方案

最后所有的错误都写成了 unknown,最后项目变成了 unknownscript,看了半天才能捋清楚项目的链路

为什么传统 try/catch 让人头疼?

先看一个典型的 TypeScript 函数:

js 复制代码
function getUser(id: string): User | undefined {
  try {
    const user = db.findUserById(id)
    return user
  } catch (e) {
    console.error(e)
    return undefined
  }
}

问题很明显:

  1. 错误类型不透明
    catch 到的 e 在 TypeScript 里是 unknown,你无法确定它到底是什么。是数据库错误?网络错误?还是业务校验失败?必须写一堆 instanceof 判断。
  2. 函数签名不体现失败
    getUser 的返回值是 User | undefined,但调用者并不知道"失败"有多少种可能,也不知道什么时候该处理错误。
  3. 控制流被打断
    try/catch 像一个安全网,一旦出错就跳出去,很难把多个可能失败的操作串成一条清晰的管道。
  4. 异步场景更痛苦
    async/await 虽然让异步代码变同步,但错误处理依然要靠 try/catch,多个异步调用嵌套时,代码可读性直线下降。

neverthrow 的哲学:错误不是异常,而是值

neverthrow 是一个轻量级的 TypeScript 库,它引入了函数式编程中的 Result 类型,帮助你把错误作为值来返回,而不是抛出异常

核心类型只有两个:

  • Result<T, E>:表示一个操作可能成功(Ok<T>)也可能失败(Err<E>
  • ResultAsync<T, E>:异步版本的 Result

通过显式地标记成功和失败类型,编译器会强制你处理所有可能的错误路径,从而在编译期就消灭大量运行时隐患。

快速上手

bash 复制代码
npm install neverthrow

同步场景:安全解析 JSON

js 复制代码
import { ok, err, Result } from 'neverthrow'

type ParseError = { message: string }

function parseJSON<T>(json: string): Result<T, ParseError> {
  try {
    return ok(JSON.parse(json) as T)
  } catch {
    return err({ message: 'Invalid JSON' })
  }
}

// 使用
const result = parseJSON<User>('{"name":"Alice"}')

result.match(
  user => console.log(user.name),        // 成功分支
  error => console.error(error.message)  // 失败分支
)

注意:函数签名 Result<T, ParseError> 明确告诉调用者"成功返回 T,失败返回 ParseError",不再需要猜测。

异步场景:请求接口

js 复制代码
import { fromPromise, ResultAsync } from 'neverthrow'

type ApiError = { status: number; message: string }

function fetchUser(id: string): ResultAsync<User, ApiError> {
  return fromPromise(
    fetch(`/api/users/${id}`).then(res => {
      if (!res.ok) {
        return Promise.reject({ status: res.status, message: 'Failed to fetch' })
      }
      return res.json() as Promise<User>
    }),
    e => e as ApiError
  )
}

// 使用
fetchUser('123').match(
  user => console.log(user.name),
  error => console.error(`${error.status}: ${error.message}`)
)

组合多个可能失败的操作

neverthrow 真正强大的地方在于可组合性 。你可以像管道一样把多个 Result 串联起来:

js 复制代码
type ValidationError = string

function validateEmail(email: string): Result<string, ValidationError> {
  if (!email.includes('@')) {
    return err('Invalid email')
  }
  return ok(email)
}

function normalizeEmail(email: string): Result<string, ValidationError> {
  return ok(email.trim().toLowerCase())
}

const result = validateEmail('  Foo@Bar.COM  ')
  .andThen(normalizeEmail)
  .map(email => ({ email }))

result.match(
  data => console.log(data.email), // "foo@bar.com"
  error => console.error(error)
)

这里 andThen 只有在前面成功时才会执行下一个函数,map 则用来转换成功值。整个流程线性、清晰、无嵌套。

太多了,neverthrow 最大的优势就是需要用户明确定义错误类型,这种处理在代码链路复杂的项目中非常有用.

实战建议

  • 团队需要统一约定 :如果代码库中一部分使用 throw,一部分使用 Result,会让风格混乱。建议在新模块或新项目中全面采用。
  • 错误类型需要设计 :如果到处定义临时错误类型,反而增加维护成本。建议在项目层面定义统一的 AppError 类型。
  • 与旧代码共存 :对于仍然抛出异常的第三方库,使用 fromThrowablefromPromise 包装一层即可。
相关推荐
自动化监测Learner6 分钟前
主流 Web 端地图引擎对比:选型指南与优劣分析
javascript
console.log('npc')23 分钟前
Git 冲突与 AI 协助指南
前端·人工智能·git·大模型
爱学堂IT分享2 小时前
Cesium可视化系统实战课程-Cesium教程学习
前端
糖墨夕2 小时前
理解大语言模型:Agent 的“大脑”
前端·agent
Rain的Java大神之路2 小时前
JavaWeb开发如何解决跨域问题
java·前端·后端·nginx·web安全·面试·运维开发
cpolar技术支持2 小时前
浏览器也能跑本地 AI:用 Transformers.js + WebGPU 做一个最小推理 Demo,cpolar 给同事远程体验
前端·ai·cpolar·webgpu·transformers.js
计算机魔术师4 小时前
OpenAI 发布 GPT-6 Astra:多项基准刷新纪录, cybersecurity 能力达 Critical 阈值
前端
xy34534 小时前
Axure9.0中继器遮罩实现方法
前端·ui·html·axure·原型·产品设计
风骏时光牛马4 小时前
智能任务自动化协同AI工作流
前端
IT_陈寒4 小时前
Python的多线程居然是个假把式?搞清GIL让我少熬三天夜
前端·人工智能·后端