简历回答逐字稿:HATEOAS 兜底状态与错误标准化处理

关联:简历追问:HATEOAS 兜底状态与错误标准化处理HATEOAS 架构下前端如何处理兜底状态或无网环境

30 秒开场

HATEOAS 做完以后,前端确实会更依赖后端契约,所以我重点做了错误标准化。否则 happy path 很漂亮,一到接口抖动、动作缺失、链式导航失败,页面就会变得不可控。

我的做法是先把错误分层:资源加载失败、资源不存在、动作不可执行、契约缺失、网络错误、鉴权失败、状态冲突。SDK 把这些错误标准化后,再给 UI 一套统一策略:哪些隐藏、哪些禁用、哪些可重试、哪些要提示刷新或重新拉取。

我会说不能简单让页面白屏,但也不能让前端凭空猜业务规则。

如果资源本身没加载出来,那页面应该进入资源不可用或重试状态。如果资源数据有缓存,但动作契约没拿到,我会把它标记成“基于旧快照”或“动作不可确认”,关键动作不应该无提示地开放。

对于弱网场景,可以使用最后一次资源快照维持页面骨架,让用户至少能查看信息。但涉及提交的动作,需要更谨慎:要么禁用并提示网络恢复后再操作,要么进入离线 mutation 队列,恢复网络后再回放,并处理 409 冲突。

如果面试官追:动作缺失和无权限怎么区分?

我会这样回答:

单纯看没有 approve,前端无法知道是无权限还是当前状态不可审批。所以如果业务需要给用户解释,就应该由后端提供更明确的 disabled reason 或者错误资源。

我会把 UI 策略分成两类:

  • 非关键动作:没有 relation 就不展示,减少干扰。
  • 关键动作:如果用户预期强,比如审批页里的审批按钮,最好展示禁用态,并由后端返回原因,比如“当前状态不可审批”或“你不是审批人”。

但无论怎么展示,前端都不应该重新写一份 status === pending && role === manager 来解释。

如果面试官追:链式 follow 中间失败怎么办?

我会说这里要避免一个页面只要某个子资源失败就整体崩掉。

比如从 root follow 到 user,再 follow 到 workspace,再 follow 到 diagram。如果 workspace 加载失败,我会保留上一级 user 状态,并让 workspace 区域显示局部错误和重试。SDK 层会把错误带上 relation path,比如:

root -> default-user -> workspace

这样 UI 和日志都知道是哪个 relation 断了,而不是只看到一个 fetch error。

如果失败是 401/403,就引导登录或权限提示;如果是 404,说明资源不存在或被删除;如果是 timeout,可以重试;如果是 invalid contract,就应该上报,因为这更像前后端契约事故。

如果面试官追:离线点击动作后,恢复网络状态变了怎么办?

我会这样说:

离线队列只能提升体验,不能绕过服务端状态机。用户离线点击 approve,本地可以生成一个 command,包含 action relation、resource id、payload、幂等 id、创建时间。UI 可以显示“待同步”,但不能说服务端已经最终成功。

网络恢复后 SDK 回放 command。如果服务端返回 409,比如这张单已经被别人撤回,我会停止乐观状态,拉取最新资源,并提示用户“操作未生效,因为资源状态已变化”。用户可以基于最新状态重新决策。

这里我会强调:离线不是前端自己变成权威状态机,最终合法性还是服务端契约和状态机决定。

如果面试官追:错误标准化有哪些类型?

我会列一个比较实用的枚举:

type ResourceError =
  | 'NetworkError'
  | 'Unauthorized'
  | 'Forbidden'
  | 'NotFound'
  | 'ActionMissing'
  | 'InvalidContract'
  | 'PayloadValidationError'
  | 'Conflict'
  | 'ServerError'

每个错误都带 resource uri、relation path、action name、http status、request id。这样 UI、日志和监控能对齐。

收尾句

这块我最核心的判断是:契约驱动 UI 不是只处理成功状态,而是要把失败状态也契约化。否则前端虽然少写了业务规则,却会在异常场景里变成不可控。