You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Automerge同步场景下,如何延迟WebSocket关闭直至文档同步完成?

解决方案:WebSocket关闭前确保CRDT变更同步完成

核心思路

拦截WebSocket的关闭触发场景(页面刷新/关闭、主动调用关闭),阻止默认关闭流程,先确认前端所有未同步的CRDT变更已被后端接收持久化,再手动完成关闭操作。

具体实现方案

1. 拦截关闭触发事件

针对不同关闭场景做拦截,进入同步校验流程:

  • 页面刷新/关闭:监听beforeunload事件
  • 主动关闭WebSocket:封装安全关闭函数,替代直接调用ws.close()
// 拦截页面刷新或关闭操作
window.addEventListener('beforeunload', async (e) => {
  const hasUnsynced = await hasUnsyncedChanges();
  if (hasUnsynced) {
    e.preventDefault();
    e.returnValue = '存在未同步的文档变更,确认离开?';
    // 等待同步完成后移除拦截,允许关闭
    await syncPendingChanges();
    window.removeEventListener('beforeunload', arguments.callee);
  }
});

// 封装安全关闭WebSocket的方法
async function safeCloseWebSocket() {
  const hasUnsynced = await hasUnsyncedChanges();
  if (hasUnsynced) {
    await syncPendingChanges();
  }
  ws.close();
}

2. 高效校验未同步变更(替代全量遍历)

放弃反复调用getAllChanges遍历所有变更的方式,通过维护本地状态优化:

  • 前端修改文档时,记录生成的变更并标记为「未同步」
  • 后端接收变更后,返回包含变更哈希的确认消息,前端移除对应未同步标记
  • 校验时只需检查未同步队列是否为空,无需全量遍历
// 本地维护未同步变更队列
let pendingChanges = [];

// 前端修改文档时记录变更
const updatedDoc = Automerge.change(originalDoc, (doc) => {
  // 执行文档修改操作
});
const changes = Automerge.getChanges(originalDoc, updatedDoc);
pendingChanges.push(...changes);
// 发送变更到后端
ws.send(JSON.stringify({ type: 'sync', changes }));

// 监听后端的同步确认消息
ws.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'sync_ack' && data.hashes) {
    // 过滤已确认的变更
    pendingChanges = pendingChanges.filter(change => 
      !data.hashes.includes(Automerge.getChangeHash(change))
    );
  }
});

// 检查是否存在未同步变更
function hasUnsyncedChanges() {
  return Promise.resolve(pendingChanges.length > 0);
}

// 同步所有未完成的变更
async function syncPendingChanges() {
  if (pendingChanges.length === 0) return;

  // 发送剩余变更
  ws.send(JSON.stringify({ type: 'sync', changes: pendingChanges }));
  
  // 等待后端确认全部变更
  await new Promise((resolve) => {
    const ackHandler = (event) => {
      const data = JSON.parse(event.data);
      if (data.type === 'sync_ack' && data.hashes.length === pendingChanges.length) {
        ws.removeEventListener('message', ackHandler);
        pendingChanges = [];
        resolve();
      }
    };
    ws.addEventListener('message', ackHandler);
  });
}

3. 超时兜底处理

避免因后端故障导致无限等待同步,设置超时时间,超时后提示用户并允许关闭:

async function syncPendingChanges() {
  if (pendingChanges.length === 0) return;

  const syncPromise = new Promise((resolve) => {
    const ackHandler = (event) => {
      const data = JSON.parse(event.data);
      if (data.type === 'sync_ack' && data.hashes.length === pendingChanges.length) {
        ws.removeEventListener('message', ackHandler);
        pendingChanges = [];
        resolve(true);
      }
    };
    ws.addEventListener('message', ackHandler);
    ws.send(JSON.stringify({ type: 'sync', changes: pendingChanges }));
  });

  // 5秒超时兜底
  const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(false), 5000));
  const syncSuccess = await Promise.race([syncPromise, timeoutPromise]);

  if (!syncSuccess) {
    alert('变更同步超时,部分内容可能未保存');
  }
}

对临时方案的优化说明

临时方案中遍历全量变更的方式在文档较大时性能损耗明显,通过维护未同步变更队列+后端确认机制,将校验复杂度从O(n)降为O(1),同时避免了不必要的全量变更遍历操作。

内容的提问来源于stack exchange,提问作者yasmanets

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 21:37:17