For the past two days I have been working with chrome asynchronous storage. It works "fine" if you have a function. (Like Below):
chrome.storage.sync.get({"disableautoplay": true}, function(e){
console.log(e.disableautoplay);
});
My problem is that I can't use a function with what I'm doing. I want to just return it, like LocalStorage can. Something like:
var a = chrome.storage.sync.get({"disableautoplay": true});
or
var a = chrome.storage.sync.get({"disableautoplay": true}, function(e){
return e.disableautoplay;
});
I've tried a million combinations, even setting a public variable and setting that:
var a;
window.onload = function(){
chrome.storage.sync.get({"disableautoplay": true}, function(e){
a = e.disableautoplay;
});
}
Nothing works. It all returns undefined unless the code referencing it is inside the function of the get, and that's useless to me. I just want to be able to return a value as a variable.
Is this even possible?
EDIT: This question is not a duplicate, please allow me to explain why:
1: There are no other posts asking this specifically (I spent two days looking first, just in case).
2: My question is still not answered. Yes, Chrome Storage is asynchronous, and yes, it does not return a value. That's the problem. I'll elaborate below...
I need to be able to get a stored value outside of the chrome.storage.sync.get function. I -cannot- use localStorage, as it is url specific, and the same values cannot be accessed from both the browser_action page of the chrome extension, and the background.js. I cannot store a value with one script and access it with another. They're treated separately.
So my only solution is to use Chrome Storage. There must be some way to get the value of a stored item and reference it outside the get function. I need to check it in an if statement.
Just like how localStorage can do
if(localStorage.getItem("disableautoplay") == true);
There has to be some way to do something along the lines of
if(chrome.storage.sync.get("disableautoplay") == true);
I realize it's not going to be THAT simple, but that's the best way I can explain it.
Every post I see says to do it this way:
chrome.storage.sync.get({"disableautoplay": true, function(i){
console.log(i.disableautoplay);
//But the info is worthless to me inside this function.
});
//I need it outside this function.
chrome.storage.sync.get
has no returned values, which explains why you would getundefined
when calling something likechrome.storage.sync.get
is also an asynchronous method, which explains why in the following codea
would beundefined
unless you access it inside the callback function.Yes, you can achieve that using promise:
Make the main function "async" and make a "Promise" in it :)
If you could manage to work this out you will have made a source of strange bugs. Messages are executed asynchronously which means that when you send a message the rest of your code can execute before the asychronous function returns. There is not guarantee for that since chrome is multi-threaded and the get function may delay, i.e. hdd is busy. Using your code as an example:
So it will be better if you use something like this:
If you really want to return this value then use the javascript storage API. This stores only string values so you have to cast the value before storing and after getting it.
Here's a tailored answer to your question. It will still be 90% long explanation why you can't get around async, but bear with me — it will help you in general. I promise there is something pertinent to
chrome.storage
in the end.Before we even begin, I will reiterate canonical links for this:
(Chrome specific, excellent answer by RobW, probably easiest to understand)
(an older but no less respected canonical question on asynchronous JS)
So, let's discuss JS asynchonicity.
Section 1: What is it?
First concept to cover is runtime environment. JavaScript is, in a way, embedded in another program that controls its execution flow - in this case, Chrome. All events that happen (timers, clicks, etc.) come from the runtime environment. JavaScript code registers handlers for events, which are remembered by the runtime and are called as appropriate.
Second, it's important to understand that JavaScript is single-threaded. There is a single event loop maintained by the runtime environment; if there is some other code executing when an event happens, that event is put into a queue to be processed when the current code terminates.
Take a look at this code:
So, what is happening here? As this code executes, when the execution reaches
.addEventListener
, the following happens: the runtime environment is notified that when the event happens (element
is clicked), it should call the handler function.It's important to understand (though in this particular case it's fairly obvious) that the function is not run at this point. It will only run later, when that event happens. The execution continues as soon as the runtime acknowledges 'I will run (or "call back", hence the name "callback") this when that happens.' If
someMoreCode()
tries to accessclicks
, it will be0
, not1
.This is what called asynchronicity, as this is something that will happen outside the current execution flow.
Section 2: Why is it needed, or why synchronous APIs are dying out?
Now, an important consideration. Suppose that
someMoreCode()
is actually a very long-running piece of code. What will happen if a click event happened while it's still running?JavaScript has no concept of interrupts. Runtime will see that there is code executing, and will put the event handler call into the queue. The handler will not execute before
someMoreCode()
finishes completely.While a click event handler is extreme in the sense that the click is not guaranteed to occur, this explains why you cannot wait for the result of an asynchronous operation. Here's an example that won't work:
You can click to your heart's content, but the code that would increment
clicks
is patiently waiting for the (non-terminating) loop to terminate. Oops.Note that this piece of code doesn't only freeze this piece of code: every single event is no longer handled while we wait, because there is only one event queue / thread. There is only one way in JavaScript to let other handlers do their job: terminate current code, and let the runtime know what to call when something we want occurs.
This is why asynchronous treatment is applied to another class of calls that:
Let's go with a classic example: AJAX calls. Suppose we want to load a file from a URL.
If we were to wait until this completes, we would have a variable, unpredictable, and relatively long delay. Because of how JS waiting works, all other handlers (e.g. UI) would not do their job for this delay, leading to a frozen page.
Sounds familiar? Yes, that's exactly how synchronous XMLHttpRequest works. Instead of a
while(1)
loop in JS code, it essentially happens in the runtime code - since JavaScript cannot let other code execute while it's waiting.Yes, this allows for a familiar form of code:
But at a terrible, terrible cost of everything freezing. A cost so terrible that, in fact, the modern browsers consider this deprecated. Here's a discussion on the topic on MDN.
Now let's look at
localStorage
. It matches the description of "terminating call to the runtime", and yet it is synchronous. Why?To put it simply: historical reasons (it's a very old specification).
While it's certainly more predictable than a network request,
localStorage
still needs the following chain:It's a complex chain of events, and the whole JS engine needs to be paused for it. This leads to what is considered unacceptable performance.
Now, Chrome APIs are, from ground up, designed for performance. You can still see some synchronous calls in older APIs like
chrome.extension
, and there are calls that are handled in JS (and therefore make sense as synchronous) butchrome.storage
is (relatively) new.As such, it embraces the paradigm "I acknowledge your call and will be back with results, now do something useful meanwhile" if there's a delay involved with doing something with runtime. There are no synchronous versions of those calls, unlike XMLHttpRequest.
Quoting the docs:
Section 3: How to embrace asynchronicity?
The classic way to deal with asynchronicity are callback chains.
Suppose you have the following synchronous code:
Suppose that, now,
doSomething
is asynchronous. Then this becomes:But what if it's even more complex? Say it was:
Well.. In this case you need to move all this in the callback.
return
must become a call instead.Here you have a chain of callbacks:
doABunchOfThings
callsdoSomething
immediately, which terminates, but sometime later callsdoSomethingElse
, the result of which is fed toif
through another callback.Obviously, the layering of this can get messy. Well, nobody said that JavaScript is a good language.. Welcome to Callback Hell.
There are tools to make it more manageable, for example Promises and
async
/await
. I will not discuss them here (running out of space), but they do not change the fundamental "this code will only run later" part.Section TL;DR: I absolutely must have the storage synchronous, halp!
Sometimes there are legitimate reasons to have a synchronous storage. For instance,
webRequest
API blocking calls can't wait. Or Callback Hell is going to cost you dearly.What you can do is have a synchronous cache of the asynchronous
chrome.storage
. It comes with some costs, but it's not impossible.Consider:
If you can put ALL your initialization code in one function
init()
, then you have this:By the time code in
init()
executes, and afterwards when any event that was assigned handlers ininit()
happens,storageCache
will be populated. You have reduced the asynchronicity to ONE callback.Of course, this is only a snapshot of what storage looks at the time of executing
get()
. If you want to maintain coherency with storage, you need to set up updates tostorageCache
viachrome.storage.onChanged
events. Because of the single-event-loop nature of JS, this means the cache will only be updated while your code doesn't run, but in many cases that's acceptable.Similarly, if you want to propagate changes to
storageCache
to the real storage, just settingstorageCache['key']
is not enough. You would need to write aset(key, value)
shim that BOTH writes tostorageCache
and schedules an (asynchronous)chrome.storage.sync.set
.Implementing those is left as an exercise.