1

I'm writing a chrome extension that collects some data about the navigation (such as cookies, clicks, local storage changes...), and in the background script i'm writing the information to chrome.storage. I need to keep the data in the order it was caught.

I get the information from multiple listeners (1 listener for clicks in content script, 1 for cookies in background script, etc...). The data is written in an array, like this:

events: [
   {
       type:"click",
       ...
   },
   {
      type:"cookie_change",
      ...
   }
]

and every time i get a piece of information i read the array "events" and i write it all again. I want to point out that i don't want to change the data structure.

The problem is that sometimes i read multiple times before writing, and i am losing the data because i'm not doing R-W-R-W but R-R-W-W. So i thought i could implement something like a mutex. I've seen some solutions to similiar questions, but i found them a bit complicated and i didn't really understand them, so i wondered if there was an easier way and i tried this one:

var busy = false;

function tryWriteEvent(someData) {
    if (busy)
        setTimeout(() => { tryWriteEvent(someData); },100);
    else {
        busy = true;
        writeEvent(someData);
    }
}

function writeEvent(someData) {
    chrome.storage.local.get(["events"], function(storage) {
        ...
        chrome.storage.local.set({events:[...]});
        busy = false;
    });
}

It works, but i don't like putting the arbitrary limit of the setTimeout function. So i wondered if there was a solution with promises, something like this (that uses a queue of promises: the next writeEvent should be waiting for the previous one):

var promises = [];

function tryWriteEvent(someData) {
    promises.push(writeEvent(someData, promises.length));
}

async function writeEvent(someData , position) {
    if (position > 0)
        await promises[position-1];

    chrome.storage.local.get(["events"], function(storage) {
        ...
        chrome.storage.local.set({events:[...]});

        promises.shift();
        return Promise.resolve();
    });
}

This thing instead doesn't work, as the promise is not returned by writeEvent but by the function inside it. I have a bunch of questions:

  • is there a way to "forward" the promise from the function inside 'chrome.storage.local.get' to 'writeEvent'?
  • is there a way to make this work using promises? Or is there another more appropriate method?

Thanks in advance!

Stephenito
  • 11
  • 1
  • Example: [Update object stored in chrome extension's local storage](https://stackoverflow.com/a/38906083) – wOxxOm Aug 20 '20 at 15:09
  • Thank you very much, it worked! I didn't see this question before, i was lost looking for mutexes and promises. – Stephenito Aug 21 '20 at 11:45

2 Answers2

0

I do not know if it will be helpful, but I have an idea:

You can save all writings into array until first read. And apply writings just before read.

const writings = []
function write(someData) {
  writings.push(someData)
}

function applyWritings(writings) {
  for (const someData of writings) {
    // DO SOMETHING
  }
}

function readData() {
  // DO SOMETHING
}

function read() {
  if (writings.length > 0) {
     applyWritings(writings)
  }
  return readData()
}
  • Thanks for your answer, but i don't think this is going to work. The problem of mine comes from the async behaviour of chrome.storage requests, and so i should use callback functions. I won't be able to use 'return readData()' because the data i'm reading is only usable in the callback function of chrome.storage.local.get(). – Stephenito Aug 21 '20 at 11:40
0

Something like it: before read or write - we wait for all previous operations to be done.

class Executor {
  constructor() {
    this.inProgress = []
  }

  async _write(params) {
    if (this.inProgress.length > 0) {
      await Promise.all(this.inProgress.map(p => p.catch(() => {})))
    }
    await write(params)
  }
  async _read(params) {
    if (this.inProgress.length > 0) {
      await Promise.all(this.inProgress.map(p => p.catch(() => {})))
    }
    await write(params)
  }

  write(params) {
      const donePromise = this._write(params)
      donePromise.finally(() => {
          const index = this.inProgress.indexOf(donePromise)
          this.inProgress.splice(index, 1)
      })
      this.inProgress.push(donePromise)
  }

  read(params) {
    const donePromise = this._read(params)
    donePromise.finally(() => {
        const index = this.inProgress.indexOf(donePromise)
        this.inProgress.splice(index, 1)
    })
    this.inProgress.push(donePromise)
  }
}