Siler
Siler

Reputation: 9484

Caching values inside a Javascript function

I have a simple Javascript method (member function of an object) which keeps an internal cache (to avoid unnecessary ajax requests.)

I must be confused about Javascript scope rules or something because updates to the cache don't seem to be visible if they're made from within a nested function.

For example:

window.mynamespace = window.mynamespace || { };
window.mynamespace.foo = function()
{
    this.cache = this.cache || { };

    alert(JSON.stringify(this.cache));

    this.cache["foo"] = "bar";
}

Here's a simple example that is meant to demonstrate that items inserted into the cache are being remembered across calls to mynamespace.foo().

In my onload or $(document).ready() handler, if I say:

window.mynamespace.foo();
window.mynamespace.foo();

I get two alerts, where the first shows me {} and the second shows me {"foo" : "bar"}. Okay great, that's exactly what I expected. So my cache seems to work.


But... if I try to update the cache from within a nested function (specifically within a callback handler of some async operation), the updates to my cache don't seem to propagate.

For example:

window.mynamespace = window.mynamespace || { };

window.mynamespace.foo = function()
{
    var instance = this;

    this.cache = this.cache || { };

    alert(JSON.stringify(this.cache));

    window.setTimeout(
        function()
        {
            instance.cache["foo"] = "bar";
        },
        0
    );
}

Here, I update the cache from within a nested function (a callback handler for window.setTimeout).

In this case, if I call window.mynamespace.foo() twice from within my document onload handler, I get two alerts, each showing me {} - an empty object. So now my cache is not updating.

Okay, so something about my understanding of Javascript scoping rules here is broken. I realize that this means something different from within a nested function, which is why I specifically use the instance variable which always refers to the window.mynamespace.foo object.

Please educate me about my error here.

Upvotes: 0

Views: 233

Answers (2)

Oriol
Oriol

Reputation: 287990

Note this is not a problem of scoping or nested functions, because the following code works:

window.mynamespace = window.mynamespace || {
  cache: {},
  foo: function() {
    alert(JSON.stringify(this.cache));
    (function() {
      window.mynamespace.cache["foo"] = "bar";
    })();
  }
};
window.mynamespace.foo();
window.mynamespace.foo();

The problem is that setTimeout code runs in the future. So of course the second alert shows an empty object, because the cache has not been modified yet.

Therefore, if you really need to update the cache asynchronously, you can use callbacks:

window.mynamespace = window.mynamespace || {
  cache: {},
  foo: function(callback) {
    alert(JSON.stringify(this.cache));
    setTimeout(function() {
      window.mynamespace.cache["foo"] = "bar";
      if(typeof callback == 'function') callback();
    }, 0);
  }
};
window.mynamespace.foo(function(){
  window.mynamespace.foo();
})();

Upvotes: 1

Vinay
Vinay

Reputation: 6322

A window.setTimeout with a 0ms wait time will clear the calling stack. What's happening is:

CLEARED CALL STACK STATE:

5) key gets set from first foo call setTimeout

6) key gets set from second foo call setTimeout

TOP FRAME OF CALL STACK:

1) foo gets called first

3) second foo call

FOO FRAME OF CALL STACK:

2) first call of foo instantiates cache and alerts empty object

4) second call of foo alerts empty object

Here is a jsfiddle that confirms this behavior:

http://jsfiddle.net/jhgeh4c5/1/

Upvotes: 2

Related Questions