try-catch捕获错误

try-catch错误捕获与处理指南

#resource / javascript #type / howto #status / growing

[!info] related notes

try-catch捕获错误


简短结论

  • 不加 try/catch 时,错误会向上传播——在不同上下文会有不同后果:可能被事件总线/框架捕获并记录、可能导致请求失败返回 500、也可能产生未处理的 Promise 拒绝 (unhandledRejection) 导致进程不稳定或崩溃。建议在边界层(应用服务/事件处理器/Controller)做有意义的捕获与错误映射。

要点(按场景)

  • 在事件处理器内被 eventBus 调用:
    • 你的 eventBus.publish 已经在调用 handler 时做了 try/catch 并记录错误(并用 Promise.allSettled)。因此 handler 抛出异常通常不会让 publish 抛出,但你会丢失业务级别的上下文日志/转换;也无法实现自定义重试/补偿逻辑。
  • 在 Express 路由(Controller)内:
    • 如果异步函数抛出并且没有被捕获且没有把错误传给 next(err),请求会变成未处理的 Promise,Express 可能不会正确返回错误,会导致请求挂起或默认 500。建议在 Controller 层捕获并转换为合适的 HTTP 响应。
  • 在应用服务层(AuthenticationApplicationService):
    • 不捕获:错误会传给调用者(Controller 或事件处理器)。这是可以接受的,只要调用方负责捕获并做正确处理。优点是不上层重复捕获;缺点是丢失局部上下文日志或无法把底层错误转换为友好业务错误。
  • 事务与资源清理:
    • 若流程涉及 DB 事务或临时资源,必须确保在异常情况下做回滚/清理——这通常需要在调用链某处(应用层或更上层)捕获并做补偿。

建议实践(简短)

  • 在应用服务内:只捕获你能处理或需要转换的错误(如密码校验、唯一约束),并抛出自定义的业务异常;不捕获则允许上层统一处理。
  • 在 Controller 层:统一捕获并把业务异常映射为 HTTP 状态码(400/409/500)。
  • 在事件处理器:仍然做必要的 try/catch 以记录上下文、实现幂等/重试或将失败入死信队列;若使用 eventBus 已捕获,也应在业务层记录与告警。
  • 全局:使用 process.on(‘unhandledRejection’) / process.on(‘uncaughtException’) 做最后的日志/告警与优雅重启(但不要依赖其做正常业务处理)。

示例:Express async 包装器(推荐用法)

export const asyncHandler = (fn: (req, res, next) => Promise<any>) => {
  return (req, res, next) => {
    fn(req, res, next).catch(next);
  };
};

示例:Controller 使用(映射错误为响应)

router.post('/register', asyncHandler(async (req, res) => {
  try {
    const account = await accountService.create(req.body);
    res.status(201).json(account);
  } catch (err) {
    if (err instanceof ValidationError) return res.status(400).json({ message: err.message });
    if (err instanceof UniqueConstraintError) return res.status(409).json({ message: err.message });
    throw err; // 交给全局错误处理器
  }
}));

总结一句话:可以让错误向上传播,但必须在边界处(Controller / 事件处理器 / 全局)有明确的捕获与映射策略,且对涉及事务的流程在捕获时做回滚/补偿。

创建于 2025/1/1 更新于 2026/5/27