Skip to content

WebSocket、离线与恢复 ​

实时连接是长生命周期资源。WebSocket 需要 open、message、error、close 状态,重连要有退避和上限;消息要有 id/版本,防止重连后重复应用。离线队列要定义顺序、幂等键、失败重试和用户可见状态。

下面的自包含状态机先验证连接协议,不需要外网或依赖包;真实 WebSocket 只是在 send/close 位置替换宿主对象。

js
function createConnection() {
  let state = 'idle'
  const log = []
  return {
    open() { state = 'open'; log.push('open') },
    send(value) { if (state !== 'open') throw new Error('not open'); log.push(`message:${value}`) },
    fail() { state = 'error'; log.push('error') },
    close(code = 1000) { state = 'closed'; log.push(`close:${code}`) },
    snapshot() { return { state, log: [...log] } },
  }
}
const connection = createConnection()
connection.open(); connection.send('ping'); connection.close()
console.log(connection.snapshot()) // open, message:ping, close:1000

接入真实 WebSocket 时,组件卸载必须调用 close();连接失败不能被当成“离线已恢复”,应单独记录 error 与下一次重连策略。

服务端事件不是本地状态的无条件覆盖:先比较版本或更新时间,再决定合并、冲突提示或重新拉取。

练习 ​

设计断网→重连→重复消息→权限过期的状态机,给每个转移写清理动作和用户反馈。

核心机制与实现 ​

网络请求有请求生命周期、缓存和取消;本地存储有同步 API、配额、版本迁移和跨标签页一致性。

围绕“WebSocket、离线与恢复”先写出输入 → 中间状态 → 可观察结果,再实现最小闭环。不要把事件监听、异步请求、DOM 读取或 store 写入混在一个函数里;每个副作用都要有创建、完成、取消和清理路径。

js
const controller = new AbortController()
const request = fetch('/api/items', { signal: controller.signal })
setTimeout(() => controller.abort(), 500)
await request

逐步运行:先只验证成功路径,再改变一个输入;记录时间顺序、对象身份、错误类型和最终可见状态。若环境不支持某个浏览器 API,要把静态协议验证和真实浏览器验证分开记录。

失败边界与变体 ​

  • 输入为空、重复、过期或非法时,不应静默覆盖有效状态。
  • 组件卸载、路由离开、标签页关闭或请求取消时,资源必须停止写入。
  • 跨文档、Shadow DOM、Worker、SSR 或多标签页会改变可见边界,不能沿用单页面直觉。
  • 需要持久化时记录 schema 版本和迁移失败策略;需要全局状态时先证明局部状态不足。

可运行实验与练习 ​

分别测试成功、HTTP 错误、网络错误、超时取消、重复提交和旧数据迁移;记录请求状态与存储版本。

练习要求:实现一个最小版本,补一个失败测试,再给出一个替代方案及其适用条件。答案应包括实际输入序列、观察结果、修复原因和未覆盖的浏览器差异。

查看本节答案

下一步 ​

完成练习后打开本章答案,再回到网络与存储课程总览。

用心记录每一步成长