0

I'm working on a rather large app, I need to call a function over an over again while a key is being pressed, at a specific interval. There's parts of the app that I can't edit, but it's replacing my .onkeyup() listeners and sometimes the interval is just left there forever. What I really want is for the interval to stop when the object gets destroyed, reassigned, etc... After setInterval(), bindings, closures, I made this to try something out and now I am even more confused:

function myInterval(){
    this.name = 'Paul'
    that=this;
this.go = function go(){
        if(that){
            this.interval = setTimeout(function(){
                console.log(that.name);
                that.go();
            },100);
        };
        console.log(that.name);
    };
};

periodic = new myInterval();
periodic.go();
setTimeout(function(){
    periodic.name='George';
}, 200);
setTimeout(function(){
    periodic.name ='John';
    periodic.go = '';
    periodic.name = 'Ringo'
}, 300);

Output:

Paul
Paul
Paul
George
George
Ringo 
> Uncaught TypeError: Property 'go' of object #<myInterval> is not a function

So, reassigning the function works, but if I replace

periodic.go = '';

with

periodic = '';

I get

Paul
Paul
Paul
George
George
John
John
...

And so on forverer; The interval never stops...

  1. Can anyone explain?

  2. Is there a common, elegant solution to ensuring I don't go dropping running intervals into the aether?

TGIWhiskey
  • 135
  • 1
  • 5
  • `The interval never stops...Can anyone explain?` Updated my answer to address that question – HMR Nov 14 '13 at 07:28

3 Answers3

3
var timer = setTimeout(func,100)

To clear it

if(timer)
clearTimeout(timer)

In case of intervals

var timer = setInterval(func,100)

To clear it

if(timer)
clearInterval(timer)

EX

function myInterval(){
    this.name = 'Paul'
    that=this;

    this.go = function go(){
        if(that){
            this.interval = setTimeout(function(){
                console.log(that.name);
                that.go();
            },100);
        };
        console.log(that.name);
    };

    this.stop = function(){ if(this.interval) clearInterval(this.interval);};
};
Voonic
  • 4,667
  • 3
  • 27
  • 58
  • 1. You should declare variables with var (or let) or they'll become global. 2. Why not take advantage of prototype? 3. When creating closures on the fly like that you could include possibly large variables that you never need but get trapped in there. – HMR Nov 14 '13 at 06:07
  • @HMR 1.Thats used by OP, so i didn't modified it to make it local 2. If you use prototype then all objects will have same timer reference variable shared among them, then how come will you stop only the one whom you want.3 Its not like that it will be trapped there – Voonic Nov 14 '13 at 06:16
  • 1. Ok, I would say that was either a typo or something. When creating multiple instances `window.that` will be overwritten and your point 2 is void since the code as is; breaks on multiple instances. 2. No, not if you do it correctly. 3. I would not count on that. I'll add a general createClosure function in my introduction to constructor function answer. To be fair; I usually don't provide "save closure creation" in many of the sample code but you should as you never know what and where you're going to add thing in your code. – HMR Nov 14 '13 at 06:23
  • @HMR I am not saying that it can't be optimized my friend, obviously you can optimize in many ways. different people use different coding styles – Voonic Nov 14 '13 at 06:27
  • I thought that using prototype would not fall under the category "coding style". It produces code that: 1. Uses less cpu 2. Uses less memory, 3 Optimizes better by JS interpreters. Yet many of the answers on SO completely ignore the fact that it exist or worse; call it a coding style. That's like saying "using classes in Java is just a coding style". This is just my opinion of course and I could be wrong. Please don't hate me for it as I would be happy to be proven wrong (I come here to learn as well). I may seem like a c4nt for commenting like this but rather comment than downvote. – HMR Nov 14 '13 at 06:47
  • @HMR Ya i am also not taking about coding style in case of prototype, i was saying in case 3, It will definitely not use prototype in this case, that's bad idea – Voonic Nov 14 '13 at 06:52
  • Yeah, I'm not totally confident with how binding is working; I wasn't sure where my loss of the binding was coming from so I left var off whenever it got me closer to what I was looking for. I admit this needs refactoring. And thanks for the info on prototype guys. I've never 'needed,' it so I've left it on the back burner but it seems it would have made a difference here in certain cases. It's about time I start using best practices even if it is just for a demo or practice anyways. – TGIWhiskey Nov 14 '13 at 18:57
  • This worked nicely for me. Very easy and to the point. – Farasi78 Jan 26 '21 at 20:42
1

The interval never stops...Can anyone explain?

Variables trapped in closure scope is the reason for that, the following code will use variable o in same scope that sets o to null.

Note: I prefer to use closure creators to limit scope and prevent other surprises. The following will create closures on they fly to keep code simple.

var o = {};
setTimeout(function(){//closure accessing o directly
   console.log("why is o null here:",o);
},100);
o=null;

The following code will use o as passed o, passed o is now trapped in the created closure scope and setting o to null does not affect passed o. Mutating o (o.something=22) will affect passed o. (google "javascript by reference by value" for references)

var o = {};
setTimeout((function(o){
  return function(){//closure accessing passed o
   console.log("why is o not here:",o);
  };
}(o)),100);
o=null;

To solve a common problem in loops creating closurs you pass the variable (i) to a function that returns a closure

for(var i = 0;i<10;i++){
  setTimeout((function(i){
    return function(){//not using i in the for loop but the passed one
     console.log("and i is:",i);//0 to 9
    };
  }(i)),100);
}

Because having the i variable in the same scope as the closure will give you unwanted results:

for(var i = 0;i<10;i++){
  setTimeout(function(){
    console.log("and i is:",i);//10 times and i is: 10
  },100);
}

Why periodic.go="" works has something to do with the pass by value by reference. How that works is shown in the following code:

function test(o){
  o=22;//no mutation but an assignment
}
var o = {name:"me"};
test(o);
console.log(o);//Object { name="me" }

function test2(o){
  o.name="you";//mutates
}
test2(o);
console.log(o);//Object { name="you"}

How to solve

I've changed your code a little to take advantage of protype for shared members (the go function) and create closures to make sure the scope of the closure is limited to what you actually need to be in there.

For more details read the introduction to constructor function and the this variable.

function MyInterval(){//capitalize constructor
    this.name = 'Paul';
    this.doContinue = true;
    this.timeoutid = false;
};
MyInterval.prototype.closures ={//creates closures with limited scope
  //closure for the go function setting the invoking object
  go:function(me){
    return function(){
      console.log("In go, name is:",me.name);
      me.go();
      //de reference "me", we no longer need it
      // can't do this in a setInterval though
      me=null;
    };
  }
}
MyInterval.prototype.go = function(){
  if(this.constructor===MyInterval){
    //if you were to call go multiple times
    if(this.timeoutid)
      clearTimeout(this.timeoutid);
    //do it again if this.doContinue is true
    this.timeoutid = (this.doContinue)? setTimeout(
      this.closures.go(this)
      //creates a closure function
      ,100
    ):false;
    return;
  };
  console.log("this is not the correct value:",this);
};
//added stop, good call from shadow, now you can stop immediately
//  or set doContinue to false and let it run it's course
MyInterval.prototype.stop = function(){
  if(this.timeoutid)
    clearTimeout(this.timeoutid);
  this.timeoutid=false;
};

periodic = new MyInterval();
periodic.go();
//because the clearTimeout you can accedentally do something silly like:
periodic.go();
periodic.go();
setTimeout(function(){
    periodic.name='George';
}, 150);
setTimeout(function(){
    periodic.name ='John';
    periodic.doContinue = false;
    periodic.name = 'Ringo'
}, 250);
Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160
  • This is it. See, I know we should hold on to our references of course but this app is mostly Ajax, and I don't have permission to mess with the back-end right now. The app was dropping .onkeyup, .onkeydown, and clearing the namespace I am in, wrenching control of the interval from my hands. I didn't wanna add a specific kludge for every case I could find. So I'll have the interval turn itself off, and a local listener that turns it back on whenever it turns itself off. That way, if the local namespace gets blasted, interval will run itself out. I think? – TGIWhiskey Nov 14 '13 at 16:53
  • @TGIWhiskey Correct, when `this.doContinue` is false then `this.timeoutid` will be set to false instead of the timeoutID that would have been returned by setting a timeout that will call this.go again. This way it'll stop doing anything until you call go again (nothing would stay in memory because no new closure is created). When calling "go" multiple times it'll throw away (clear) the timeout set by the first call and set a new one so you won't have multiple timeouts running at the same time. – HMR Nov 16 '13 at 12:45
1

In your code:

function myInterval(){
    this.name = 'Paul'
    that=this;

that becomes a global when you first call myInterval and the above line is executed, I think you mean to keep it local. Otherwise, that will reference the last instance created, which is not necessarily the "current" instance.

Anyhow, the best way to do this is to give instances a stop method. So you do:

// By convention, constructors start with a capital letter
function MyInterval() {

    // Use a more suitable name than "that" to reference the instance
    var interval = this;
    this.name = 'Paul'

    // This keeps calling itself every 100ms
    this.go = function go(){

      // Keep a reference to the timeout so it can be cancelled
      this.timeout = setTimeout(function(){
                       console.log('setTimeout: ' + interval.name);
                       interval.go();
                     }, 100);
      console.log('Go: ' + interval.name);
    };

    // Stop the timeout
    this.stop = function() {  
      if (interval.timeout) {
        clearTimeout(interval.timeout);
      }
    };
};

var periodic = new MyInterval();

// In 100 ms, it will log its name
periodic.go();

// In 200 ms its name will be changed
setTimeout(function(){
    periodic.name='George';
}, 200);

// In 500 ms its name will be changed and it will be cancelled.
setTimeout(function(){
    periodic.name ='John';

    // call stop here
    periodic.stop();
    periodic.name = 'Ringo'
}, 500);
RobG
  • 142,382
  • 31
  • 172
  • 209