14

I'm building a Chrome extension and I wrote this code.

var Options = function(){};

Options.prototype = {

    getMode: function(){
               return chrome.storage.sync.get("value", function(e){  
                 console.log(e); // it prints 'Object {value: "test"}'.       
                 return e;
               });
    },

    setMode: function(){
        chrome.storage.sync.set({"value": "test"}, function(e) {         
        })
    }
}

var options = new Options();
options.setMode();
console.log(options.getMode()); // it prints 'undefined'.

I expected it to print

Object {value: "set up"}

when I call options.getMode(), but it prints undefined.

Does anyone know how to fix this problem?

Qantas 94 Heavy
  • 15,750
  • 31
  • 68
  • 83
crzyonez777
  • 1,789
  • 4
  • 18
  • 27

3 Answers3

23

The chrome.storage API is asynchronous - it doesn't return it directly, rather passing it as an argument to the callback function. The function call itself always returns undefined.

This is often used to allow other methods to run without having to wait until something responds or completes - an example of this is setTimeout (only difference is that it returns a timer value, not undefined).

For example, take this:

setTimeout(function () { alert(1); }, 10000);
alert(0);

Because setTimeout is asynchronous, it will not stop all code until the entire function completes, rather returning initially, only calling a function when it is completed later on - this is why 0 comes up before 1.


For this reason, you cannot simply do something like:

// "foo" will always be undefined
var foo = asyncBar(function (e) { return e; }); 

Generally, you should put what you want to do in your callback (the function that is called when the asynchronous function is completed). This is a fairly common way of writing asynchronous code:

function getValue(callback) {
  chrome.storage.sync.get("value", callback);
}

You could place an entire portion of your code inside the callback - there's nothing stopping you from doing so. So instead of doing the following:

console.log(getValue()); // typical synchronous method of doing something

This would probably be a better idea:

// how it would be done in asynchronous code
getValue(function (value) {
  console.log(value);
}); 
Community
  • 1
  • 1
Qantas 94 Heavy
  • 15,750
  • 31
  • 68
  • 83
  • >this is why 0 comes up before 1. I see. Thanks!! It's very clear. – crzyonez777 Sep 09 '13 at 13:25
  • >var foo; >asyncBar(function (e) { foo = e; }); I think I tried something like this. But, I'll try it again now! – crzyonez777 Sep 09 '13 at 13:26
  • I tried this code. But, it returns undefined at the end of code in the post. var test; chrome.storage.sync.get("value", function(e){ console.log(e); test = e; }); – crzyonez777 Sep 09 '13 at 13:29
  • This is the whole code without setMode(). var Options = function(){}; Options.prototype = { getMode: function(){ var test; chrome.storage.sync.get("value", function(e){ console.log(e); test = e; return test; }); } } var options = new Options(); console.log(options.getMode()); – crzyonez777 Sep 09 '13 at 13:38
  • 1
    @crzyonez777: I seem to have misled you. That still wouldn't work for the same reasons I have outlined above - that was meant for if you were to use that (much) later somewhere else, `test` would still be undefined and return out of the function straight after that call. – Qantas 94 Heavy Sep 09 '13 at 13:44
  • so, this doesn't work either. var Options = function(){}; Options.prototype = { getMode: function(callback){ chrome.storage.sync.get("value", callback); } } var options = new Options(); var test = options.getMode(function (e) { return e; }); console.log(test); – crzyonez777 Sep 09 '13 at 13:54
  • So, I should give up wrapping it with getter method. – crzyonez777 Sep 09 '13 at 13:55
  • so, I should define methods that doesn't return anything if I use asynchronous callback. – crzyonez777 Sep 09 '13 at 14:04
  • @crzyonez777: well any return value isn't likely to have much effect, so (basically) yes. – Qantas 94 Heavy Sep 10 '13 at 00:18
  • So `localStorage` is synchronous and `chorme.storage` is asynchronous. What a nightmare if you're targeting both browser and chrome app. In my situation the UI updates before the settings are obtained from `chrome.storage`, so I must update the UI again in the callback! – bryc Jan 21 '17 at 15:17
  • Can you use 'await' to fix this? – Niels Oct 09 '17 at 08:30
  • Maybe there isn't a valid value yet ? – Omiod Jul 15 '19 at 07:25
10

Chrome storage API is asynchronous and it uses callback, that's why you're getting this behavior.

You can use Promise API to overcome this asynchronous issue, which is simpler and cleaner. Here is an example:

async function getLocalStorageValue(key) {
    return new Promise((resolve, reject) => {
        try {
            chrome.storage.sync.get(key, function (value) {
                resolve(value);
            })
        }
        catch (ex) {
            reject(ex);
        }
    });
}

const result = await getLocalStorageValue("my-key");
Taohidul Islam
  • 5,246
  • 3
  • 26
  • 39
  • Keep in mind that the `value` in that function is an object and if you want to get exactly the _value_ of the requested key you should use `resolve(value[key]);` instead of `resolve(value);` – DemX86 Aug 13 '21 at 10:48
  • 2
    Thank you. One snarky comment towards Google devs: Was it a good idea to name an **async** method literally **sync**? Who would ever guess… – HynekS Jul 27 '22 at 19:07
2

The chrome.storage.sync.get is called asynchronously, that's why you have to pass it a callback, so that it is executed in the future.

When you try to print the returned value of getModeyou are printing the return value of whatever chrome.storage.sync.get returns after queuing the asynchronous call to be executed.

This is a common mistake people do in javascript, while they are learning to use asynch calls.

fmsf
  • 36,317
  • 49
  • 147
  • 195