EN

错误处理

使用局部 catch 和全局处理器处理定义错误、运行时错误及可打印输入错误。

更新于 2周前

不同错误需要不同的处理方式。重复命令名是开发缺陷,应阻止应用启动;网络失败可能需要补充命令上下文; 对于无效选项,通常向 CLI 用户输出一条简明的 stderr 信息即可。

func 通过错误分类和固定的处理顺序区分这些情况。你可以使用默认的输入错误输出,也可以为某个命令添加局部 catch,或注册全局格式化器。无需在每个处理器外重复包裹 try/catch

错误分类

分类谁应修复示例处理方式
F_SYSTEM开发者重复 token、缺失处理器、无效装饰器目标或不支持的选项类型。立即抛出,不会传递给局部或全局用户处理器。
F_RUNTIME应用或依赖处理器抛错、服务请求失败,或业务代码拒绝某项操作。局部 catch 可以处理;未处理的错误会传递给已注册的全局处理器。
F_RUNTIME_PRINTCLI 用户输入未知选项、解析失败、处理器标志冲突或校验失败。使用相同的错误处理流程;默认向 stderr 输出消息,全局处理器可以关闭该输出。

修复系统错误,而不是格式化它

系统错误表示应用图或装饰器声明无效:命令与选项 token 重复、存在多个主命令或缺失命令作用域、 选项类型不支持、处理器缺失,或装饰器参数无效。它们会在用户错误处理器之前抛出,并且应该让开发 检查失败。不要把它转换成“请重试”这类用户提示,应直接修复定义。

当 funcgo 输出 F_SYSTEM_* 追踪链接时,可以在错误索引中查看对应错误码的复现与修复方式。

处理单个命令的错误

@Catch() 装饰同一个命令类的方法。当 func 为该命令的字段赋值、执行校验或运行处理器时产生非系统错误,该方法会接收错误对象。

src/commands/publish.command.ts
import { Catch, Command, Exception, FuncException, Handler } from 'func'

@Command({ name: 'publish' })
export class PublishCommand {
  @Catch()
  onError(@Exception() exception: FuncException) {
    console.error(`Publish failed: ${exception.message}`)
  }

  @Handler()
  async run() {
    throw new Error('Registry is unavailable')
  }
}
点击终端以聚焦

局部 catch 正常结束后,func 会认为该错误已经处理。全局处理器不会运行,func 也不会输出默认信息。因此,局部方法必须负责输出、恢复或记录。如果局部 catch 自己抛错,新错误会传给全局处理器。

当某个命令需要专门的恢复或提示时,可以使用局部 catch。例如,清理未完成的发布, 或在错误信息中加入目标 registry。所有命令共用的格式规则应写在全局处理器中。

统一格式化所有错误

@CatchAll() 注册一个错误处理器类。@CommandError() 是为了 兼容保留的等价名称。虽然用户不能把它选为命令,但仍要把该类注册到模块的 commands 列表中。

src/commands/error.command.ts
import { CatchAll, Exception, FuncException } from 'func'

@CatchAll()
export class ErrorHandler {
  constructor(@Exception() exception: FuncException) {
    if (exception.level === 'runtime-print') {
      console.error(`Invalid input: ${exception.message}`)
      exception.preventDefaultPrint()
      return
    }

    console.error(`Unexpected error: ${exception.message}`)
  }
}
src/app.module.ts
import { FuncModule } from 'func'
import { ErrorHandler } from './commands/error.command'
import { PublishCommand } from './commands/publish.command'

@FuncModule({
  commands: [PublishCommand, ErrorHandler],
})
export class AppModule {}

普通抛出值和原生 Error 到达全局处理器前,会被归一化为 F_RUNTIME 处理器错误。FuncException 包含消息、分类、归一化错误和 details,可直接用于记录。

避免输入错误重复输出

全局处理器运行后,func 会默认把 F_RUNTIME_PRINT 的消息输出到 stderr。如果全局处理器已经输出自定义信息,请像上例一样调用 preventDefaultPrint() 。如果默认消息已经满足需求,无需在全局处理器中再次输出。

内置解析和字段校验会自动创建 runtime-print 错误。业务代码如果发现其他可由用户修正的输入问题,并希望使用相同的输出规则,可以使用 createRuntimePrintError()

TypeScript
import { F_RUNTIME_PRINT, createRuntimePrintError, errorTypes } from 'func'

throw createRuntimePrintError(F_RUNTIME_PRINT.VALIDATION, errorTypes.INPUT, 'Token is required.', { option: 'token' })

什么时候需要自定义错误处理

  • func 默认输入信息已经足够时,不添加自定义处理器。
  • 单个命令需要恢复或补充命令上下文时,使用 @Catch()
  • 需要统一前缀、结构化日志、翻译或遥测时,使用 @CatchAll()
  • 正常命令结果写入 stdout,失败信息写入 stderr。
  • 不要在用户输出或日志中包含 token、密码或携带完整凭据的请求。