1

I'm using Vanilla javascript for now.

The problem is - I have a IndexedDB where I keep events (let's call them punches). The logic is whenever user punches something, a new record is created in IndexedDB. The record looks like this:

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

I have a dedicated field "sychronized" (1/0) in my IndexedDB record, that for each punch carries information whether this particular punch was already synced with server or not.

I have a procedure that is triggered whenever the browser comes back online, that should loop through all records that haven't been synced yet (synchronized=0), send them to the server and IF (AND ONLY IF) the server responds OK, to set synchronized=1.

However I'm struggling with how both IndexedDB and HTTPRequests are asynchronous.

This is the pseudocode I have so far, but I realize it does not work properly (it will update synchronize=1 even if the call to server fails).

    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();
        }
      }
    }

I tried to understand the Promises from the doc, but boy I got lost.

Any help on how to implement this logic appreciated.

1 Answers1

1

you can use Promises and async/await syntax, as follow incorporates promise and ensures that the synchronization only happens if the server call is successful:

    function syncPunch(punch) {
  return new Promise((resolve, reject) => {
    const httpRequest = new XMLHttpRequest();

    if (!httpRequest) {
      reject(new Error("XMLHttpRequest not supported"));
    }

    httpRequest.onreadystatechange = () => {
      if (httpRequest.readyState === 4) {
        if (httpRequest.status === 200) {
          resolve();
        } else {
          reject(new Error("Server request failed"));
        }
      }
    };

    httpRequest.open(
      "GET",
      "https://server/punch.php?controlid=123&teamid=123&timestamp=ABC...."
    ); // fake call
    httpRequest.send();
  });
}

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;

      try {
        await syncPunch(punch); // Wait for the server call to complete

        console.log("Now fake-syncing " + JSON.stringify(punch));
        punch.synchronized = 1;
        const updateRequest = cursor.update(punch);
        console.log("Updated " + punchKey);
      } catch (error) {
        console.error("Failed to sync punch:", error);
      }

      cursor.continue();
    }
  };
}
  • Thank you for pointing me in the right direction! The problem is now, that upon updating the data in Indexed DB - I get error "Transaction is finished" because those lines after "await syncPunch(punch)" occur at different time and the cursor is not locked during the http request. This is explained here - https://stackoverflow.com/questions/61296252/failed-to-execute-put-on-idbobjectstore-the-transaction-has-finished – Adam Muller Jun 27 '23 at 14:49
  • Do not wrap requests in promises like this because the transaction is not kept alive between awaits – Josh Jul 03 '23 at 14:10