0

Ok, so basically i'm creating an Interval class to handle repeating actions. I have something like this:

function Interval(fn, speed) {
 this.fn = fn;
 this.speed = speed;
 this.nt = setInterval(fn, speed);
}

And then i have 3 methods:

 this.pause = function() {
  clearInterval(this.nt);
 }

 this.start = function() {
  this.nt = setInterval(this.fn, this.speed);
  return this.nt;
 }

 this.wait = function(time) {
  this.pause();
  setTimeout(function() {
   this.start();
  }, time);
 }

The problem appears in the third method. this.pause(); and this.start(); works as expected. But when I nest this.start into a setTimeout function it stops working. I don't understand why. Here's an example:

var i = 0:
var nt = new Interval(function() {
    alert('Itineration: '+ i );
    if(i>5);
    nt.pause();
    setTimeout(nt.start, 2000);
    // nt.wait(2000);
  }, 500);

Neither nt.wait(2000); nor nt.pause(); setTimeout(nt.start, 2000); is working.

Fran Cano
  • 667
  • 1
  • 9
  • 18
  • The answer below is correct, but in your code why do you have `if(i>5);` ? – Miguel Mota Mar 18 '15 at 05:01
  • This question has been asked about a dozen times. Search harder for an answer. Start off with http://stackoverflow.com/questions/2749244/javascript-setinterval-and-this-solution. Or, just debug your code. Place a breakpoint inside `this.start`, and examine the value of `this`. Voila. –  Mar 18 '15 at 05:18

3 Answers3

4

this inside the timeout handler is not the Interval object, it is referring to the window object(not strict mode) so this.start() will not work

One solution is to pass a custom context using Function.bind()

this.wait = function (time) {
    this.pause();
    setTimeout(function () {
        this.start();
    }.bind(this), time);
    // setTimeout(this.start.bind(this), time) as @elclanrs suggested
}
Arun P Johny
  • 384,651
  • 66
  • 527
  • 531
  • 3
    Why not `setTimeout(this.start.bind(this), time)`? – elclanrs Mar 18 '15 at 04:59
  • Would like to give -1 for failing to point out that this is a frequent duplicate and not pointing to a more authoritative answer. –  Mar 18 '15 at 05:19
0

You are running into a context issue with your code. When the setTimeout function executes your callback the definition of "this" is no longer your Interval object. You need to modify your code so that you maintain a proper reference to the Interval object.

this.wait = function(time) {
    var interval = this;
    interval.pause();
    setTimeout(function() {
        interval.start();
    }, time);
}

Edit

I just saw the other answer using .bind which is a much cleaner solution from a readability standpoint. One important note about .bind is that behind the scenes it basically generates another function to call your original function using the .call or .apply methods to set the correct value of this

In most cases the readability gained from using .bind is worth it. However, if this is going to be a core component to a larger system, it is a good idea to squeeze every ounce of performance you can out of it. Which would be an argument for avoiding .bind in this specific situation.

Shawn Lehner
  • 1,293
  • 7
  • 14
  • What you say is "one important note about **.bind** is not a note, it's actually the fundamental thing that `bind` does for a living. Your point about performance is quite misplaced. First, you have no way to know that calling `bind` is going to be faster or slower than writing out the anonymous function inline. Second, the probability that whatever difference there is is going to affect the performance of the app is vanishingly small. –  Mar 18 '15 at 05:26
  • Agree on both points. As a said in my answer, bind is a much more readable and pleasant solution. Again, to my point, it is more of a black box. The performance varies by browser and if you are trying to squeeze every ounce of performance, you might opt to not use it. See: http://stackoverflow.com/questions/8656106/why-is-function-prototype-bind-slow – Shawn Lehner Mar 18 '15 at 05:48
0

Working example based on the other answers.

function Interval(fn, speed) {
    this.fn = fn;
    this.speed = speed;
    this.nt = setInterval(fn, speed);

    this.pause = function () {
        clearInterval(this.nt);
    }

    this.start = function () {
        this.nt = setInterval(this.fn, this.speed);
        return this.nt;
    }

    this.wait = function (time) {
        this.pause();
        setTimeout(function () {
            this.start();
        }.bind(this), time);
    }
}

var i = 0;
var nt = new Interval(function () {
    document.write('<pre>Itineration: ' + i + '</pre>');
    i++;
    nt.wait(2000);  
}, 500);
Miguel Mota
  • 20,135
  • 5
  • 45
  • 64