这类问题看似集中在一个 API 或参数上,真正决定可用性的却是状态归属、资源上限和失败后的恢复方式。 乐观插入能让聊天立即响应,但本地出现不等于对方收到。状态需要单向推进,每一步由对应证据驱动;若失败回滚整条消息,用户会失去内容和重试入口。
前端架构的核心不是目录层级,而是状态由谁拥有、事件能否重复执行、旧异步结果如何失效,以及卸载后监听器和资源是否真正归零。
先把实现决策说清楚
落地时要把关键选择变成稳定契约:输入如何校验、状态由谁推进、资源何时释放、旧版本如何降级。只有这样,后续优化才不会改变原有语义。
- 客户端生成稳定消息 ID,本地立即显示 queued,服务端或远端不重新编号。
- DataChannel send 只推进 sending,远端 ACK 才推进 delivered,已读另有回执。
- 重试复用原 ID 与内容,状态机拒绝迟到 ACK 让终态倒退。
乐观 UI 可以提前显示用户意图,不能提前伪造远端事实。把每个状态绑定到清楚证据,速度和诚实才可以同时成立。
最容易漏掉的失败路径
生产问题往往发生在两个正确动作同时到达时。需要逐条检查迟到消息、重复执行、资源耗尽和版本交叉,而不是只补当前堆栈显示的那一行。
- 把 bufferedAmount 入队当送达,对方离线后界面仍显示成功。
- 失败后生成新 ID,旧消息与重试同时收到 ACK,聊天出现两条。
- 已读回执暴露用户阅读行为却没有隐私开关或产品说明。
上线前怎样验证
用已知输入和可控故障建立金样本,然后把线上低基数指标与测试结果对齐。只有指标能提前识别同类退化,验证才真正延伸到生产。
- 在 queued、sending、delivered 和 read 每阶段断线并刷新双方。
- 丢弃 ACK 后重试并晚到原 ACK,确认只存在一条且状态不倒退。
- 关闭已读回执时验证协议不发送阅读事件,界面也不暗示已读。
完成并不等于主路径跑通,而是每个终态都能对账、每个自动动作都受用户意图约束、每个成本都有明确上限。