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

Vanilla JS下IndexedDB未同步数据与服务器同步的异步逻辑实现求助

问题描述

我用Vanilla JavaScript开发,用IndexedDB存储名为punches的事件记录,记录结构示例:

{punchedon: 1687522698067, controlpointid: '1', method: '3', qrcodetext: '4', latitude: '5', ... synchronized: 0}

每条记录包含synchronized字段(值为1/0),标记是否已与服务器同步。

需要实现的逻辑:浏览器恢复联网时,遍历所有synchronized=0的未同步记录,发送至服务器,仅当服务器响应成功时,将该记录的synchronized字段更新为1。

但现有伪代码存在逻辑问题——即使服务器请求失败,仍会将synchronized设为1,伪代码如下:

function syncPunch(punch) {
    httpRequest = new XMLHttpRequest();

    if (!httpRequest) {
      return false;
    }
    
    httpRequest.onreadystatechange = alertContents;
    httpRequest.open("GET", "https://server/punch.php?controlid=123&teamid=123&timestamp=ABC...."); // fake call
    httpRequest.send();
}

function syncUnsynced() {  // sync all not yet synced punches
  let tx = db.transaction(['punches'], 'readwrite');
  let store = tx.objectStore('punches');
  let syncIndex = store.index('synchronized'); // I have an index on the synchronized field, I query only those synchronized=0
  let keyRng = IDBKeyRange.only(0);  
  
  const cursorRequest = syncIndex.openCursor(keyRng);
  cursorRequest.onsuccess = e => {
    const cursor = e.target.result;
    if (cursor) {
        const punch = cursor.value;
        const punchKey = cursor.value.key;
        
        synchPunch(punch);  // here I need to wait (react on / callback / promise??) to make sure the call was succesfull
        
        console.log("Now fake-syncying " + JSON.stringify(punch));
        punch.synchronized = 1;
        const updateRequest = cursor.update(punch);  // if this update fails and the call was sucesfull, I can live with that. But in normal scenario, I don't want to sync something that has been synced already.
        console.log("Updated " + punchKey);

        cursor.continue();
    }
  }
}

我尝试理解Promise相关文档但未能掌握,恳请帮助实现正确的同步逻辑。


解决方案

1. 把syncPunch改造为返回Promise的函数

将异步HTTP请求封装成Promise,这样就能明确等待请求完成,区分成功/失败状态:

function syncPunch(punch) {
  return new Promise((resolve, reject) => {
    const httpRequest = new XMLHttpRequest();
    if (!httpRequest) {
      reject(new Error("无法创建XMLHttpRequest对象"));
      return;
    }

    httpRequest.onreadystatechange = function() {
      if (httpRequest.readyState === XMLHttpRequest.DONE) {
        // 可根据实际业务调整成功判断条件,比如状态码200+服务器返回成功标识
        if (httpRequest.status === 200) {
          const response = httpRequest.responseText;
          // 假设服务器返回包含"success"表示记录同步成功
          if (response.includes("success")) {
            resolve(true);
          } else {
            reject(new Error("服务器返回失败响应"));
          }
        } else {
          reject(new Error(`请求失败,状态码:${httpRequest.status}`));
        }
      }
    };

    // 用punch的真实数据拼接请求URL
    const url = `https://server/punch.php?controlid=${punch.controlpointid}&teamid=123&timestamp=${punch.punchedon}`;
    httpRequest.open("GET", url);
    httpRequest.send();
  });
}

2. 改造syncUnsynced函数,处理异步流程

利用async/await配合IndexedDB游标,确保前一条记录的同步请求完成后再处理下一条:

async function syncUnsynced() {
  const tx = db.transaction(['punches'], 'readwrite');
  const store = tx.objectStore('punches');
  const syncIndex = store.index('synchronized');
  const keyRng = IDBKeyRange.only(0);  

  const cursorRequest = syncIndex.openCursor(keyRng);
  
  cursorRequest.onsuccess = async (e) => {
    const cursor = e.target.result;
    if (cursor) {
      const punch = cursor.value;
      const punchKey = cursor.key; // 注意:用cursor.key获取记录主键,而非cursor.value.key

      try {
        console.log("正在同步记录:" + JSON.stringify(punch));
        // 等待同步请求完成
        await syncPunch(punch);
        // 同步成功,更新synchronized字段
        punch.synchronized = 1;
        // 等待IndexedDB更新操作完成
        await new Promise((resolve, reject) => {
          const updateRequest = cursor.update(punch);
          updateRequest.onsuccess = () => resolve();
          updateRequest.onerror = () => reject(updateRequest.error);
        });
        console.log("记录同步并更新成功:" + punchKey);
      } catch (error) {
        console.error("记录同步失败:", error);
        // 同步失败,不更新字段,留待下次重试
      }

      // 处理下一条记录
      cursor.continue();
    } else {
      console.log("所有未同步记录处理完成");
    }
  };

  cursorRequest.onerror = (e) => {
    console.error("获取未同步记录失败:", e.target.error);
  };
}

3. 监听联网状态恢复事件

添加浏览器在线状态监听,自动触发同步:

window.addEventListener('online', () => {
  console.log("网络已恢复,开始同步未上传记录");
  syncUnsynced();
});

关键说明

  • Promise封装HTTP请求:将异步请求转为Promise,让我们能精准等待请求结束,区分成功/失败场景。
  • 异步游标处理:用async/await控制游标流程,避免并发请求过多导致的服务器压力或数据混乱。
  • 错误容错:通过try/catch捕获同步过程中的错误,失败时不修改synchronized字段,保留记录以便后续重试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 20:05:08