Clear 'looped' timeout closed in function - how to return id of interval that I would have ability to clear it

74 Views Asked by At

I have two functions inside my Class. First fires timeout and looped it (works like interval) and second to clearTimeout. My problem is, that clearTimeout doesn't work. How can I fix it?

this.startMove = function() {
    setTimeout(function handler() {
        self.moveMonster(moveMonsterCallback);
        setTimeout(handler, self.speed);
    }, this.speed);
};

this.stopMove = function() {
    clearTimeout(self.startMove());
}

For example I want to run these functions on click.

3

There are 3 best solutions below

1
On BEST ANSWER

Let me know if this works for you :

this.startMove = function() {
    self.timeout = setTimeout(function handler() {
        self.moveMonster(moveMonsterCallback);
        self.startMove();
    }, self.speed);
};

this.stopMove = function() {
    clearTimeout(self.timeout);
}

So basically I have reused the @Michael Horn's solution. And, I am happy to use this answer as an to his edited answer (if the question resolves).

8
On

You need to assign your timeout to a handler:

this.startMove = function() {
    self.timeout = setTimeout(function handler() {
        self.moveMonster(moveMonsterCallback);
        setTimeout(handler, self.speed);
    }, this.speed);
};

this.stopMove = function() {
    clearTimeout(self.timeout);
}

Edit

As michalgrzasko mentions, the above code doesn't work. The reason being that the handle is assigned to the wrong timeout function. The outer timeout is only set once, while the inner timeout is called in a recursive loop, thus that is the timeout which needs to be cleared.

this.startMove = function() {
    setTimeout(function handler() {
        self.moveMonster(moveMonsterCallback);
        self.handle = setTimeout(handler, self.speed);
    }, this.speed);
};

this.stopMove = function() {
    clearTimeout(self.timeout);
}

However

Again, as michalgrzasko and several others point out, the better solution is to use setInterval, as it is easier to read, and thus safer:

this.startMove = function() {
    self.moveInterval = setInterval(function handler() {
        self.moveMonster(moveMonsterCallback);
    }, this.speed);
};

this.stopMove = function() {
    clearInterval(self.moveInterval);
}
1
On

If you use setTimeout or setInterval and want the ability to cancel them, you need to save the returnvalue of this function in a variable. The return-value is a handler, that can be used in clearTimeout or clearInterval.

In this scenario setInterval would be easier if the moveMonster function has a smaller time consumption than this.speed.

this.startMove = function() {
    this.startMove.intervalHandler = setInterval(function handler() {
        self.moveMonster(moveMonsterCallback);
    }, this.speed);
};

this.stopMove = function() { clearInterval(this.startMove.intervalHandler); }

I prefer using the function scope to save such things like Handler. If you don't like it use the parent scope or the context

If it's important to use setTimeout instead of setInterval you have solution in other answers. It's simply the same: Save the returnvalue of setTimeout und use it later with clearTimeout.


If moveMonsterCallback should be called if the animation is over, take a look at promises instead of callbacks. With callbacks you're getting fast in a position, it's hard to track the callings.


A word on animations: They're tricky! Don't use setTimeout. Better call requestAnimationFrame on the window object.

function step(timestamp) {
  if (!step.startTime) step.startTime = timestamp;
  var delta = step.getDelta(timestamp);
  // do the calculations of how far you have to move the monster in this time range
  // move the monster...
  ...
  // animation is stopped? If not: We want to animate another timeframe
  if (!step.stopped) {
    window.requestAnimationFrame(step);
  }
}
step.startTime = null;
step.stopped = false;
step.getDelta = (timestamp)=>timestamp-step.startTime;

function stopMove() {
  step.stopped = true;
}

window.requestAnimationFrame(step);

This solution provides a better result, because it's independent of the precision of setTimeout. It works with time differences