JavaScript setInterval and `this` solution
Asked Answered
C

9

82

I need to access this from my setInterval handler

prefs: null,
startup : function()
    {
        // init prefs
        ...
        this.retrieve_rate();
        this.intervalID = setInterval(this.retrieve_rate, this.INTERVAL);
    },

retrieve_rate : function()
    {
        var ajax = null;
        ajax = new XMLHttpRequest();
        ajax.open('GET', 'http://xyz.example', true);
        ajax.onload = function()
        {
            // access prefs here
        }
    }

How can I access this.prefs in ajax.onload?

Confetti answered 1/5, 2010 at 8:1 Comment(1)
to see a good answer with 3 solutions check this: https://mcmap.net/q/244349/-referencing-quot-this-quot-inside-setinterval-settimeout-within-object-prototype-methods-duplicateLaundromat
N
95

The setInterval line should look like this:-

 this.intervalID = setInterval(
     (function(self) {         //Self-executing func which takes 'this' as self
         return function() {   //Return a function in the context of 'self'
             self.retrieve_rate(); //Thing you wanted to run as non-window 'this'
         }
     })(this),
     this.INTERVAL     //normal interval, 'this' scope not impacted here.
 );

Edit: The same principle applies to the " onload ". In this case its common for the "outer" code to do little, it just sets up the request an then sends it. In this case the extra overhead an additinal function as in the above code is unnecessary. Your retrieve_rate should look more like this:-

retrieve_rate : function()
{
    var self = this;
    var ajax = new XMLHttpRequest();
    ajax.open('GET', 'http://xyz.example', true);
    ajax.onreadystatechanged= function()
    {
        if (ajax.readyState == 4 && ajax.status == 200)
        {
            // prefs available as self.prefs
        }
    }
    ajax.send(null);
}
Nielsen answered 1/5, 2010 at 8:12 Comment(9)
I was going to do this initially, but then I remembered this pattern is really most useful for loops.Junker
@Matthew Flaschen: It just as useful for this scenario as it is for loops.Sternforemost
@Anthony: so the trick with self is the only option here? can you confirm that the solution by Matthew will not work?Confetti
@Michael: First of all its not a "trick" its just how things work in Javascript. Matthew's answer as it currently stands at time of writing this comment doesn't work. There was an earlier version of it that might have worked but it involved passing this as a parameter which was unnecessary and awkard (any caller of retrieve_rate would have know this unnecessary special requirement).Nielsen
passing this as an argument to the (function(self){...})(this) in setInterval didn't work for me because the function is executed immediately instead of being delayed. @Joel Fillmore's solution works for meDoublefaced
I wish I had known about this 12 hours ago. Going to permanently etch it into my brain now. Thanks!Detrimental
If you're using Underscore.js, you could use _.bind which does this for you: this.intervalID = setInterval(_.bind(this.retrieve_rate, this), this.INTERVAL);Creswell
That's a lot of brackets over thereFryd
Why you didn't do in first code snippet same as second code: store the this ref in a local var as var self = this; and simply use that in setInterval ?Laundromat
A
111
this.intervalID = setInterval(this.retrieve_rate.bind(this), this.INTERVAL);
Ahasuerus answered 11/2, 2014 at 20:24 Comment(5)
This is the right solution. The accepted solution seems to require unnecessarily more code.Biofeedback
But this method has a drawback. Most likely, it will not work with older versions of IEAhasuerus
@Ahasuerus Worth noting. But its still a much cleaner solution.Tranquilize
It's supported since IE9 so is a clean solution for me.Jemimah
If you need support for IE8 and you're using Underscore.js, you could use _.bind: this.intervalID = setInterval(_.bind(this.retrieve_rate, this), this.INTERVAL);Creswell
N
95

The setInterval line should look like this:-

 this.intervalID = setInterval(
     (function(self) {         //Self-executing func which takes 'this' as self
         return function() {   //Return a function in the context of 'self'
             self.retrieve_rate(); //Thing you wanted to run as non-window 'this'
         }
     })(this),
     this.INTERVAL     //normal interval, 'this' scope not impacted here.
 );

Edit: The same principle applies to the " onload ". In this case its common for the "outer" code to do little, it just sets up the request an then sends it. In this case the extra overhead an additinal function as in the above code is unnecessary. Your retrieve_rate should look more like this:-

retrieve_rate : function()
{
    var self = this;
    var ajax = new XMLHttpRequest();
    ajax.open('GET', 'http://xyz.example', true);
    ajax.onreadystatechanged= function()
    {
        if (ajax.readyState == 4 && ajax.status == 200)
        {
            // prefs available as self.prefs
        }
    }
    ajax.send(null);
}
Nielsen answered 1/5, 2010 at 8:12 Comment(9)
I was going to do this initially, but then I remembered this pattern is really most useful for loops.Junker
@Matthew Flaschen: It just as useful for this scenario as it is for loops.Sternforemost
@Anthony: so the trick with self is the only option here? can you confirm that the solution by Matthew will not work?Confetti
@Michael: First of all its not a "trick" its just how things work in Javascript. Matthew's answer as it currently stands at time of writing this comment doesn't work. There was an earlier version of it that might have worked but it involved passing this as a parameter which was unnecessary and awkard (any caller of retrieve_rate would have know this unnecessary special requirement).Nielsen
passing this as an argument to the (function(self){...})(this) in setInterval didn't work for me because the function is executed immediately instead of being delayed. @Joel Fillmore's solution works for meDoublefaced
I wish I had known about this 12 hours ago. Going to permanently etch it into my brain now. Thanks!Detrimental
If you're using Underscore.js, you could use _.bind which does this for you: this.intervalID = setInterval(_.bind(this.retrieve_rate, this), this.INTERVAL);Creswell
That's a lot of brackets over thereFryd
Why you didn't do in first code snippet same as second code: store the this ref in a local var as var self = this; and simply use that in setInterval ?Laundromat
W
19

The default behavior of setInterval is to bind to the global context. You can call a member function by saving a copy of the current context. Inside retrieve_rate the this variable will be correctly bound to the original context. Here is what your code would look like:

var self = this;
this.intervalID = setInterval(
    function() { self.retrieve_rate(); },
    this.INTERVAL);

Bonus tip: For a plain function reference (as opposed to an object reference which has a member function) you can change the context by using JavaScript's call or apply methods.

Wed answered 22/10, 2011 at 21:9 Comment(2)
This worked for me, the call to 'call' doesn't seem to be needed though. The context of retrieve_rate should be set to self by default, because it is called as a member function.Lepp
@Lepp - you are right, I remembered solving this for a callback function reference where it was needed. I've fixed the answer, thanks!Wed
L
19

With improving browser support the time is now good to use the EcmaScript 6 enhancement, the arrow => method, to preserve this properly.

startup : function()
    {
        // init prefs
        ...
        this.retrieve_rate();
        this.intervalID = setInterval( () => this.retrieve_rate(), this.INTERVAL);
    },

Using => method preserves the this when retrieve_rate() is called by the interval. No need for funky self or passing this in parameters

Lancey answered 16/7, 2016 at 6:11 Comment(0)
U
10

window.setInterval(function(){console.log(this)}.bind(this), 100)

this is legal in javascript and saves lots of code :)

Unprovided answered 15/10, 2016 at 9:6 Comment(0)
A
3

This would be the cleanest solution, since most of the time you actually want to switch the this context for your consecutive method calls:

Also it's easier to grasp the concept of.

    // store scope reference for our delegating method
    var that = this;
    setInterval(function() {
        // this would be changed here because of method scope, 
        // but we still have a reference to that
        OURMETHODNAME.call(that);
    }, 200);
Ampersand answered 5/11, 2014 at 14:48 Comment(0)
B
3

With modern browsers the setInterval method allows additional parameters which are passed through to the function specified by func once the timer expires.

var intervalID = scope.setInterval(func, delay[, param1, param2, ...]);

Hence, a possible solution can be:

this.intervalID = setInterval(function (self) {
        self.retrieve_rate();
    }, this.INTERVAL, this);

A demo:

var timerId;
document.querySelector('#clickMe').addEventListener('click', function(e) {
    timerId = setInterval(function (self) {
        self.textContent = self.textContent.slice(0, -1);
        if (self.textContent.length == 0) {
            clearInterval(timerId);
            self.textContent = 'end..';
        }
    }, 250, this);
})
<button id="clickMe">ClickMe</button>
Barouche answered 17/9, 2018 at 21:18 Comment(0)
J
0
prefs: null,
startup : function()
    {
        // init prefs
        ...
        this.retrieve_rate();
        var context = this;
        this.intervalID = setInterval(function()
                                      {
                                          context.retrieve_rate();
                                      }, this.INTERVAL);
    },

retrieve_rate : function()
    {
        var ajax = null;
        ajax = new XMLHttpRequest();
        ajax.open('GET', 'http://xyz.example', true);
        var context = this;
        ajax.onload = function()
        {
            // access prefs using context.
            // e.g. context.prefs
        }
    }
Junker answered 1/5, 2010 at 8:4 Comment(7)
this inside the function passed to setInterval, will refer to the global object. Did you mean context.retrieve_rate instead of this.retrieve_rate ?Goffer
This has evolved in the right direction, don't need the context being passed as parameter though.Nielsen
@Matthew, Anthony So how do I access it from onload? Tried this.prefs, but didn't work...Confetti
You're welcome Matthew, by the way, you don't need to use call, context.retrieve_rate() is enough, since you have a base object (context.)Goffer
Thanks again, @Anthony and @CMS. :) It's easy to get unnecessarily "clever" when closures are in play.Junker
Couldn't access it neither with this nor with context... If you know how exactly, please update the access code.Confetti
@Pablo, context.prefs should work in the onload. I would recommend using a library supporting AJAX (like jQuery), though.Junker
V
-1

That's not a beauty solution but it's in common usage:

var self = this;
var ajax = null;
//...
ajax.onload = function() {
    self.prefs....;
}
Vair answered 1/5, 2010 at 8:4 Comment(1)
The problem is how setInterval calls the retrieve_rate function, the this value inside the method refers to the global object...Goffer

© 2022 - 2024 — McMap. All rights reserved.