I'm having a lot of trouble with setInterval and class methods - javascript

I keep running into bizarre problems. I've been unable to find anything on them after doing some research, so I thought I'd come here to present them. I have a class which is rather long, but I'll include the relevant bits:
class AnimatedSnake {
constructor(canvasId, coordinates) {
this.coordinates = coordinates;
this.direction = 2;
this.ctx = document.getElementById(canvasId).getContext("2d");
// 0 - .99, describes how far along snake is between coordinates
this.progress = 0;
}
erase() {
for (let i = 0; i < this.coordinates.length; i++) {
let c1 = this.coordinates[i][0],
c2 = this.coordinates[i][1];
this.ctx.clearRect(c1 * 31, c2 * 31, 31, 31);
}
}
next() {
this.progress += 0.01;
if (this.progress >= 1) {
this.progress %= 1;
let nextCoord = this.coordinates[4].slice();
nextCoord[0] += ((this.direction % 2) * this.direction);
nextCoord[1] += ((!(this.direction % 2) * (this.direction / 2)));
this.coordinates.push(nextCoord);
this.coordinates.shift();
}
console.log(this.erase);
this.erase();
this.draw();
}
}
So far, I can call AnimatedSnake.next() indefinitely if I'm doing it manually (i.e. from the console). However, when I put the function in an interval or timeout - setInterval(AnimatedSnake.next, 100) - it all of a sudden, on the first run, claims that AnimatedSnake.erase is not a function. I tried putting AnimatedSnake.erase() directly in the interval, and when I do THAT, for some absurd reason it goes and tells me that it cannot take the length property of AnimatedSnake.coordinates, which it claims is undefined. Nowhere in my code to I redefine any of these things. coordinates is altered, but it should not be undefined at any point. And erase is of course a method that I never change. Does anyone have any insight into why, when these are called with setInterval or setTimeout weird things happen, but if I call the functions repeatedly (even in a for loop) without the JavaScript timing functions everything works out fine? I'm genuinely stumped.

Consider these two snippets:
animatedSnake.next()
And:
let method = animatedSnake.next;
method();
In the first snippet next is called as a member of animatedSnake object, so this within the context of next method refers to the animatedSnake object.
In the second snippet the next method is detached from the object, so this no longer refers to the animatedSnake instance when the method function is invoked. This is how passing a method to another function, like setInterval works. You can either use Function.prototype.bind method for setting the context manually:
setInterval(animatedSnake.next.bind(animatedSnake), 100)
or wrap the statement with another function:
setInterval(() => animatedSnake.next(), 100)

Related

change the content of a div from inside a javascript class

Hello dear coding buddies,
I have a problem:
Im trying to change the content of a div with the results of a method in a class i made. Somehow i keep ending up with null. I am probably doing something conceptually wrong but i have no idea what since i am pretty new to javascript.
This is my code so far:
class Virus {
constructor(population) {
this.mortalityPercentage = 0.023;
this.period = 1;
this.infectionRate = 2;
this.infected = 1;
this.population = population;
}
calculate() {
while (this.infected < this.population) {
this.period += 6;
console.log(this.period);
this.infected *= this.infectionRate;
console.log(this.infected);
this.dead = this.infected * this.mortalityPercentage;
console.log(this.dead);
}
}
render() {
document.getElementById('days').innerHTML = this.period;
document.getElementById('infected').innerHTML = this.infected;
document.getElementById('dead').innerHTML = this.dead;
}
};
let virus = new Virus(7760000000);
virus.calculate();
virus.render();
How many days does it take to infect the world?
<div id="days">
</div>
Number of infected people:
<div id="infected">
</div>
I see dead people:
<div id="dead">
</div>
This is the error message i get on jsFiddle:
Uncaught TypeError: Cannot set property 'innerHTML' of null
at Virus.render ((index):55)
at (index):63
Ideally i would like a counter making use of setInterval or requestAnimationFrame but i have no idea how. Can you please help?
Your setInterval could go in a bunch of places, but it's probably most clear to put it in around your function calls:
setInterval( () => {
virus.calculate();
virus.render();
}, 250)
Now the calculate and render methods will be executed every 250ms
You can replace the while inside your calculate with an if since it will be evaluated every time the function is called
calculate() {
if (this.infected < this.population) {
this.period += 6;
this.infected *= this.infectionRate;
this.dead = this.infected * this.mortalityPercentage;
}
}
Working example: https://jsfiddle.net/yodjf14v/3/
Alternatively, you could put the setInterval inside the calculate() function and have it call render():
calculate() {
setInterval( () => {
if (this.infected < this.population) {
this.period += 6;
this.infected *= this.infectionRate;
this.dead = this.infected * this.mortalityPercentage;
this.render(); // Here's the render call
}
}, 250)
}
For extra goodness, make the 250ms in your setInterval a property of the you class like this.interval = 250;. Having the value in the middle of the code is known as a magic number and is often considered bad practice.
requestAnimationFrame probably wouldn't be a good match for your use case here because you don't have direct control over when it runs (ie: every n milliseconds). It's generally more used for animation when you want to repaint as often as possible.
You could always throttle your function calls within it by storing the last called time and comparing it to the current time, but for this use case it would just be setInterval with more steps

Why does this JavaScript function only work once?

I have tried hard to find out why this function only runs once, but none of the solutions seem to apply (e.g. declaring a variable the same name as a function). The following function "scroll()" scrolls my page at the required speed to coordinate with the audio reading being played. It works great, but only works once without doing a page refresh.
Where am I going wrong?
function scroll() {
var element = document.getElementById("scrolldiv");
var scrollingheight = element.scrollHeight - window.innerHeight;
var thirdscreen = (window.innerHeight * .31);
var halfscreen = (window.innerHeight * .69);
var delaypercent = (thirdscreen / element.scrollHeight) * 100;
var speedpercent = (halfscreen / element.scrollHeight) * 100;
var dur = $("#jquery_jplayer_audio_1").data("jPlayer").status.duration;
var speedcalc1 = (dur * speedpercent) / 100;
var speedcalc2 = dur - speedcalc1;
var speed = (speedcalc2 * 1000) / scrollingheight;
speed2 = (Math.round(speed * 4) / 4).toFixed(2);
var delay = (delaypercent * dur) * 10;
setTimeout(function(){
scrollinterval = setInterval("scrollmove()", speed2);
}, delay);
}
function scrollmove() {
if (localStorage.getItem("scrolling") == 'on') {
var position = $('#scrolldiv').scrollTop();
$('#scrolldiv').scrollTop(position + 1);
}
}
I realize my code could be written more elegantly, but for now I am wrestling with getting it working. Thanks for any input
As we know setTimeout() will only execute one time, and setInterval() repeats. When you invoke either of these methods, you're creating a variable object that contains the associated callback. When that object is dropped -- setTimeout() exectutes or setInterval() is canceled using clearInterval() -- the object gets nullified and garbage collected. When that happens, the callback it contained and all its variables are zapped too.
Because you're nesting the setInterval() as part of a callback in a setTimeout(), your setInterval() is dropped once the setTimeout() executes. You need to find another way to invoke setInterval();

Understanding JavaScript setTimeout and setInterval

I need a bit of help understanding and learning how to control these functions to do what I intend for them to do
So basically I'm coming from a Java background and diving into JavaScript with a "Pong game" project. I have managed to get the game running with setInteval calling my main game loop every 20ms, so that's all ok. However I'm trying to implement a "countdown-to-begin-round" type of feature that basically makes a hidden div visible between rounds, sets it's innerHTML = "3" // then "2" then "1" then "GO!".
I initially attempted to do this by putting setTimeout in a 4-iteration for-loop (3,2,1,go) but always only displayed the last iteration. I tried tinkering for a bit but I keep coming back to the feeling that I'm missing a fundamental concept about how the control flows.
I'll post the relevant code from my program, and my question would be basically how is it that I'm writing my code wrong, and what do I need to know about setTimeout and setInterval to be able to fix it up to execute the way I intend it to. I'm interested in learning how to understand and master these calls, so although code examples would be awesome to help me understand and are obviously not unwelcome, but I just want to make it clear that I'm NOT looking for you to just "fix my code". Also, please no jQuery.
The whole program would be a big wall of code, so I'll try to keep it trimmed and relevant:
//this function is called from the html via onclick="initGame();"
function initGame(){
usrScore = 0;
compScore = 0;
isInPlay = true;
//in code not shown here, these objects all have tracking variables
//(xPos, yPos, upperBound, etc) to update the CSS
board = new Board("board");
ball = new Ball("ball");
lPaddle = new LPaddle("lPaddle");
rPaddle = new RPaddle("rPaddle");
renderRate = setInterval(function(){play();}, 20);
}
.
function initNewRound(){
/*
* a bunch of code to reset the pieces and their tracking variables(xPos, etc)
*/
//make my hidden div pop into visibility to display countdown (in center of board)
count = document.getElementById("countdown");
count.style.visibility = "visible";
//*****!!!! Here's my issue !!!!*****//
//somehow i ends up as -1 and that's what is displayed on screen
//nothing else gets displayed except -1
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
}
.
//takes initNewRound() for-loop var i and is intended to display 3, 2, 1, GO!
function transition(i){
count.innerHTML = (i === 0) ? "Go" : i;
}
.
//and lastly my main game loop "play()" just for context
function play(){
if(usrScore < 5 && compScore < 5){
isInPlay = true;
checkCollision();
moveBall();
moveRPaddle();
if(goalScored()){
isInPlay = false;
initNewRound();
}
}
}
Thanks a bunch for your advise, I'm pretty new to JavaScript so I really appreciate it.
Expanding on cookie monster's comment, when you use setInterval in a loop, you are queueing up method executions that will run after the base code flow has completed. Rather than queue up multiple setInterval executions, you can queue up a single execution and use a variable closure or global counter to track the current count. In the example below, I used a global variable:
var i = 3 // global counter;
var counterInterval = null; // this will be the id of the interval so we can stop it
function initNewRound() {
// do reset stuff
counterInterval = setInterval(function () { transition() }, 1000); // set interval returns a ID number
}
// we don't need to worry about passing i, because it is global
function transition() {
if (i > 0) {
count.innerHTML = i;
}
else if (i === 0) {
count.innerHTML = "Go!";
}
else {
i = 4; // set it to 4, so we can do i-- as one line
clearInterval(counterInterval); // this stops execution of the interval; we have to specify the id, so you don't kill the main game loop
}
i--;
}
Here is a Fiddle Demo
The problem is in this code:
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
When the code runs, it creates a new function 3 times, once for each loop, and then passes that function to setInterval. Each of these new functions refers to the variable i.
When the first new function runs it first looks for a local variable (in it's own scope) called i. When it does not find it, it looks in the enclosing scope, and finds i has the value -1.
In Javascript, variables are lexically scoped; an inner function may access the variables defined in the scope enclosing it. This concept is also known as "closure". This is probably the most confusing aspect of the language to learn, but is incredibly powerful once you understand it.
There is no need to resort to global variables, as you can keep i safely inside the enclosing scope:
function initNewRound(){
var i = 3;
var count = document.getElementById("countdown");
count.style.visibility = "visible";
var interval = setInterval(function(){
//this function can see variables declared by the function that created it
count.innerHTML = i || "Go"; //another good trick
i-=1;
i || clearInterval(interval); //stop the interval when i is 0
},1000);
}
Each call to this function will create a new i, count and interval.

Integer returning as NaN when added

Writing some code, and when creating an instance of a class, something strange happens with an integer variable I have:
function Mat(x, y, spawner) {
this.x = x;
this.y = y;
this.val = 1;
this._spawner = spawner;
this.newborn = true;
this.bornTime = 0;
this.spawnTimer = setInterval("this.bornTime++; console.log(this.bornTime);", 1000);
}
Pretty cut and clear code; every second after an instance of the variable is created, it should increment the bornTime variable by 1 and log it.
Mat.prototype.update = function() {
if (this.bornTime >= 5) {
this.bornTime = null;
clearInterval(this.spawnTimer);
this.newborn = false;
console.log("Grown!");
}
}
This additional code would cause this instance to be "grown" after 5 seconds, however when I check the console, it reads that bornTime is not a number(NaN).
Why is this, and is there a solution that I am not seeing?
this inside the setTimeout code is not the same as outside (more info on MDN), so your code is actually calculating undefined++, which is NaN.
You have to create another variable, and pass a function to setTimeout instead of letting it eval a string (by the way, passing a function is supposed to be faster, and looks better):
var that = this;
this.spawnTimer = setInterval(function(){
that.bornTime++;
console.log(that.bornTime);
}, 1000);
I know this is 5 years old question but its 2018 and heres an Es6 syntax solution to avoid extra step of binding key word this.
this.spawnTimer = setInterval(() => {
this.bornTime++;
console.log(this.bornTime);
}, 1000);

Why object does not see his method?

I am developing a game using the framework atomJS and library libCanvas. Here is the code where the error occurs:
var Planet=atom.Class({
//other code
clearLayer : function (layer) {
layer.ctx.clearRect(this.x, this.y, this.size, this.size);
},
colonize : function (layer, angle, color,ms) {
**this.clearLayer(layer);**
drawArc({
context: layer.ctx,
x: Math.round(this.x + this.size / 2),
y: Math.round(this.y + this.size / 2),
radius: this.radius + 5,
width: 4,
color: color,
opacity: 0.6,
angleFinish: angle
});
if (this.colonizing) {
//if (this.cursorOnPlanet()) this.context.fillText(COLONIZING, (this.x + this.size / 2) - 30, this.y + this.size - 2);
this.colonizingTimer = setTimeout(this.colonize, ms,layer, angle + 5, color,ms);
if (angle > 360) {
this.colonizing = false;
this.state = 1;
}
} else {
clearTimeout(this.colonizingTimer);
this.clearLayer(layer);
}
},
});
On this line, this.clearLayer(layer); the script terminates with an error Object [object DOMWindow] has no method 'clearLayer'.Tell me please what's the problem?
Thanks!
It's important to see how whateverObject.colonize() is actually getting called. Anyway, it's clear that the original object's method is being bound to a different object before getting called. This is fairly common in event handlers, for example, where this usually (but not always) ends up being the event target, not the method's original object.
It's common for developers to use a closure to ensure that they have a safe reference for the original this. For example, you might define colonize in a constructor that says var self=this;, which would guarantee the name self points to the original this even if this itself gets rebound.
Another approach is to use Function.prototype.bind (which you'd have to polyfill for old JS engines), which creates a new function with a this object guaranteed to be whatever you specify.
It sounds like the function is being called from the DOM window and not the local class. When the this object is a window, you'll inevitably have scoping issues.
Your problem is with the setTimeout function. When the timeout is called, it's telling the DOMWindow, not the local class, to call the function. To fix this, wrap the call into a function.
function(){<code>}
Edit: I'm not really sure of the purpose of the extra fields in the setTimeout, so I omitted my solution. If you wrap whatever you're doing in a function, it should work though.
change
this.colonizingTimer = setTimeout(this.colonize, ms,layer, angle + 5, color,ms);
to
var self = this;
this.colonizingTimer = setTimeout(function(){self.colonize.call(self);}, ms,layer, angle + 5, color,ms);
The thing is that because of the timeout, the this object is removed from your object scope and at execution time refers to the global object(window) which has no method named clearLayer.
Here's a simplified demo to see the difference.
& the most correct way is to use "delay":
this.colonizingTimer = this.colonize.delay(ms, this, [layer, angle + 5, color, ms]);
But, if i understand right you want to animate angle from zero to 360 degrees? Why dont you use "Animatable" & ".animate" ?
With every question about LibCanvas you can send me an email to shocksilien#gmail.com

Categories

Resources