I've got problems with the storage

Hello, I am trying to write my first extension for Firefox.
This extension should get the title and URL of all the open tabs, and then store these information inside the local storage in this form:

{
    1: {title: "Google", url: "https://google.com"},
    2: ...,
    3: ...
}

Once some tabs are stored, there is a button available to open all the stored tabs at once.
This button, when no tabs are stored, is disabled.
As the user saves some tabs, it should become available, but this doesn’t happen.

I’ve defined the function to access the storage and I’ve set a listener to the onChanged event of the storage like this

let getStorage = browser.storage.local.get();
browser.storage.onChanged.addListener(updateButtonsState);

the listener is triggered and it calls this function that should change the status of the buttons:

function updateButtonsState() {
    // Get the data from the storage
    getStorage.then(list => {
        // If no tabs are stored
        if (Object.keys(list).length === 0) {
            // Set the button to open the stored tabs as disabled
            getStoredBtn.setAttribute("disabled", '');
            // Set the button to delete the storage as disabled
            delStoredBtn.setAttribute("disabled", '');
         }
        else {
            // Set the button to open the stored tabs as enabled
            getStoredBtn.removeAttribute("disabled");
            // Set the button to delete the storage as enabled
            delStoredBtn.removeAttribute("disabled");
        }
    });
}

Even if inside the storage inspector I can see the tabs stored, the function above sees the storage as empty, if I refresh the page, the button is set as active, if I refresh again, it is disabled. In addition, a function to clear the storage seems to don’t work properly.
I do not understand this behavior.

The code posted doesn’t cover all the process, which would be a lot of code to post. If you want, you can see the full code on Github and even download it to see it in action.
This is the link: https://github.com/LuigiCaradonna/TabsUrl

Just as a quick guide (ask me if you need more details):

  • the entry point is bg.js which wakes cs.js as the extension’s button is clicked
  • cs.js initializes a modal window (modal.js) containing the list of the open tabs and 4 buttons at the bottom
  • manage_storage.js contains the code which interacts with the local storage
  • save_text.js has its own purpose, it saves the links of the open tabs inside a text file, it works correctly and it is not involved with the troublesome process

Do you see where the problem is?

Browser.storage.local.get() returns a promise. You are storing that promise in a variable. Each time the storage is updated, you seem to be looking at the same original promise each time, unless I’m misunderstanding the code.

It seems like you are trying to use the variable as a shorter way to call the stroage functions, but that’s not what you are doing.

Try calling browser.storage.local.get() directly in your update function and see that makes a difference.

1 Like

That’s correct, thank you.

I misunderstood the use of a variable to store the promise, I thought it would just become as an alias for browse.storage.local.get().

Bonus tip:
Use “async/await” to make work with promises easier:

For example:

async function work() {
  const storageData = await browser.storage.local.get();
  console.log(storageData);  // stored object, not a promise anymore!
  return storageData
}

work();   // returns promise that resolves to storage data

Sorry for going a bit offtopic…

People claims async/await makes things easier, but so far I’m only getting more confused by it, and hates when for example MDN documentation only has examples of something using async/await. When trying to use async/await in practice I cannot get anything to work as expected, so I always end up using the “classic” way of asynchronous programming in my coding.
Also your example confuses me. You claim the console.log gets the stored object, not a promise. However you also claims that work() “returns a promise that resolves to storage data”. In my experience the last is correct, and I don’t understand what that async/await actually do. In my experience this does the same as your work() function:

function work() {
  const storageData = browser.storage.local.get();
  return storageData
}
work();   // returns promise that resolves to storage data

Who can be the first to explain async/await in a way so also I understand what it does and what advantages it might give? :blush:

Disclaimer: I have not tried execute any of above code.

Okay while writing above I got the feeling that I finally understood. I have had that feeling before, only to later find out I actually still didn’t get it. So maybe not. But am I right that?:…

Both versions of work() returns a promise that resolves to the storage data. But even though they resolve to exactly the same, it is actually not the same promises. You could say that the first work() function (by juraj) returns a promise representing the work() function itself. While my work() function returns the promise coming from browser.storage.local.get(). But the practical difference here is only what you can do (or how you can do it) inside the work() functions.

Maybe I get it this time. Maybe. Maybe I later realize I still don’t get it…

The important part of my example is not the returned value (sorry for confusing example), it’s the console.log, which can print the data received from the storage without using a callback “then” function.

You see, in the async function the program can “wait” for the asynchronous work to finish and when it’s done, it will continue. So in my example, the execution of code will stop on the line:

  const storageData = await browser.storage.local.get();

And only after the promise returned by browser.storage.local.get() is resolved, it will put the result into the “storageData” and continue with execution.

Also, note that every “async” function always returns a promise - even if you return a normal value or promise, for example:

async function getValue() {
  return 9;
}
getValue() // returns a Promise<9>

The biggest benefit of using async/await is once you start using them in conditional code, like “for” loops.
For example this is my typescript code for processing asynchronous work one by one:

type AsyncWork<T> = () => Promise<T>;
const empty = Symbol();

// faster version - unlike "executePromisesSync" function, this one returns only OK values (it ignores errors), so no need to map/filter result array
export async function executeOkPromisesSync<T>(arrayOfFn: AsyncWork<T>[]): Promise<T[]> {
  const result: T[] = [];
  for (let i = 0; i < arrayOfFn.length; i++) {
    const work = arrayOfFn[i];
    const value = await work().catch(() => empty);    // I really wanted to use Symbol somewhere :)
    if (value !== empty) result.push(value as T);
  }
  return result
}

1 Like

Thanks juraj
I think I get it. At least I feel it makes sense. I’ll have to see next time I try using it if I’m in more luck than previous attempts.
I don’t know typescript. But I think I have an idea of what your loop-example does?:
Looping an array of “async-functions”, but executing them “synchronous” (not multitasking) and return array of resolved values from (only the) successful executions.

1 Like