返回知识库

数据与服务 · 运行与可靠性

错误处理Error Handling

检测错误、加上下文、决定怎么处理(翻译 / 降级 / 重试 / 失败),给用户可行动的反馈,给系统可追查的日志——而不是崩溃、静默吞掉或原样泄漏。

错误处理实验室:同一个错误,四种接法

一个订单查询接口面对四种故障。你触发错误,再选择处理策略——吞掉、原样抛出、带上下文抛出、降级——看响应、日志和用户提示分别长成什么样。

边界 try / catch 接住错误后,你怎么处理决定用户和系统看到什么。 先选一种故障,点「发送请求」看未处理时的原始错误,再选处理策略,观察响应、日志和用户提示如何变化。

GET /api/orders/ord_882订单查询接口

你的处理函数怎么接住这个错误?(先点「发送请求」触发错误)

提示:数据库断开 + 带上下文抛出,是最稳的受控失败。

四条规则:分类、翻译、留痕、降级

面板里刚操作过的东西,命名成可复述的规则。

  • 绝不静默吞:空 catch 把错误当成功,是最坏的选择——响应 200 空体、无日志,问题被掩埋,监控也不会告警。
  • 不原样抛:把原始错误甩给客户端,状态码是吓人的 500,响应体暴露堆栈、文件路径、内网地址;既危险也无用。
  • 翻译成有意义错误:把底层错误映射到合适的 HTTP 状态——校验失败 400、不存在 404、暂时故障 503/504——并给一句可行动提示。
  • 区分预期和意外:预期错误(字段错、找不到)是正常结果,记 INFO/WARN、返回 4xx;意外故障(DB 断、OOM)记 ERROR/告警、返回 5xx。
  • 响应记结论,日志记细节:用户提示和响应体只放可公开内容;requestId、目标地址、字段名、耗时这些细节放进日志(链接 log)。

什么时候用

错误处理不是“加个 try/catch 就行”,而是任何可能失败的边界都要做的决策。

看到这些信号就要想“怎么处理”:调用数据库或下游服务、解析外部输入、写文件或发请求、执行可能抛异常的库函数。不适合的处理方式:在生产代码里用空 catch 压住一切、把所有异常都当成 500、把原始 stack 直接渲染给用户、 或者在循环里无限重试一个不会自己好的错误(比如权限不足)。

怎么用

从一次失败到“用户有下一步、系统能追查”的最短路径。

  1. 在请求边界 try / catch,给每次请求注入 requestId 并透传到日志和下游。
  2. 分类错误:是校验失败、不存在,还是依赖故障?据此选择 4xx、5xx 或降级。
  3. 翻译:构造有意义的 HTTP 状态 + 面向用户的提示;响应体不带堆栈和内部细节。
  4. 留痕:在日志里记 level、requestId、目标、字段、耗时(链接 log、monitoring);敏感字段脱敏。
  5. 读接口可降级到缓存;写接口对幂等操作有限重试(链接 idempotency),持续失败要熔断或排队。

正反例:同一笔 DB 故障

只改一个关键选择:catch 之后是吞掉,还是翻译成受控失败。

反例 · 吞掉try { return await db.orders.find(id) } catch (e) { return null } // 200 空 → 用户以为成功,监控瞎

数据库断了却返回 200 空:脏数据落库、用户无感知、日志无记录、告警不触发。事后追查时两眼一抹黑。

正例 · 翻译 + 留痕try { return await db.orders.find(id) } catch (e) { log.error('db unreachable', { requestId, target, id }) return response(503, { error: 'service_unavailable', retry_after: 30 }) }

翻译成 503 + retry_after,告诉客户端是暂时故障可重试;日志按 ERROR 记目标和 requestId,监控据此告警;响应不泄内部细节。

快速自测

写订单时数据库连接断了。下列处理最合适的是?

继续查证

术语的技术定义和行为以这些一手或权威资料为准。

下一步学

和本知识点经常一起出现的概念。