Too much recursion on simple JS slider - javascript

I am trying to build a simple slider by changing the background image, but i am not sure why I am getting an error that says too much recursion.
document.addEventListener("DOMContentLoaded", function(event) {
let headerImages = [];
let header = document.querySelector('.header');
let i = 0;
let time = 3000;
headerImages[0] = 'img/header1.jpg';
headerImages[1] = 'img/header2.jpg';
function changeHeaderImg() {
header.style.backgroundImage = "url(" + headerImages[i] + ")";
if(i < headerImages.length - 1){
i++;
} else {
i = 0;
}
setTimeout(changeHeaderImg(), time);
}
changeHeaderImg();
});

You are calling changeHeaderImg and passing it's result to setTimeout instead of passing changeHeaderImg itself.
So you are getting endless recursion which results in so-called "stack overflow" classic error.
Try setTimeout(changeHeaderImg, time);

A function that calls itself is called a recursive function. Once a condition is met, the function stops calling itself. This is called a base case.
In some ways, recursion is analogous to a loop. Both execute the same code multiple times, and both require a condition (to avoid an infinite loop, or rather, infinite recursion in this case). When there are too many function calls, or a function is missing a base case, JavaScript will throw this error.
function loop(x) {
if (x >= 10) // "x >= 10" is the exit condition
return;
// do stuff
loop(x + 1); // the recursive call
}
loop(0);
Setting this condition to an extremely high value, won't work:
function loop(x) {
if (x >= 1000000000000)
return;
// do stuff
loop(x + 1);
}
loop(0);
// InternalError: too much recursion
This recursive function is missing a base case. As there is no exit condition, the function will call itself infinitely.
function loop(x) {
// The base case is missinng
loop(x + 1); // Recursive call
}
loop(0);
// InternalError: too much recursion

Related

JS die roll simulation with 6 die images

in Js, I want to try to simulate a die roll by showing images of die 1 to 6, but when I try to display these in a for loop, it only displays image dice6. I tried putting in a nested for loop to slow down the outer loop but that didnt work. Does the page need to refresh after changing "src" attribute?
const dieImage = function (num) {
return "images/dice" + String(num).trim() + ".png";
};
function dieRoll(num) {
for (let i = 1; i < 7; i++) {
for (let z = 0; z < 44444; z++) {} // attempt to slow
if (num === 1) {
img1.setAttribute("src", dieImage(i));
} else {
img2.setAttribute("src", dieImage(i));
}
}
}
As mentioned in the comments you can use setTimeout. You can introduce a delay and give the browser a chance to redraw by using setTimeout, promise, and await, for example like this:
const DELAY = 300; // 300ms
async function dieRoll(num) {
for (let i = 1; i < 7; i++) {
if (num === 1) {
img1.setAttribute("src", dieImage(i));
} else {
img2.setAttribute("src", dieImage(i));
}
await new Promise((resolve) => setTimeout(() => resolve(), DELAY));
}
}
The loop execution will stop until the promise is resolved, but it will let the browser to continue to respond and redraw. When the promise is resolved after the timeout callback is run after the given DELAY milliseconds, the next iteration of the loop will take place.
What you are missing (roughly) is that the browser paints the screen when the JavaScript code has finished running. Even though you are setting the src attribute to a different image in a loop, the JavaScript code finishes only when the loop ends. The browser paints only once, i.e. the last image you set in the loop. This explanation may be oversimplified, but it gives you an idea.
The solution is to return from the JavaScript code after setting the src and repeating after a suitable delay, giving the user the opportunity to sense the change. setTimeout is probably good enough for your case; in other use cases where you want really smooth animation, there would be other solutions (e.g. requestAnimationFrame()). An untested implementation to demonstrate the intent:
function dieRoll(selectedNum) {
var counter = 8; // how many times to change the die
function repeat() {
if (counter === 1) {
// on the last iteration, set the image representing the selected number
img1.setAttribute("src", dieImage(selectedNum));
} else {
// else decrement the counter, set a random image and repeat after a timeout
counter--;
img1.setAttribute("src", dieImage(Math.floor(6 * Math.random()) + 1));
setTimeout(repeat, 300);
}
}
repeat();
}

What can I change in my recursive function to make it work correctly?

function loo(x) {
if (x >= 10) {
return;
loo(x+1)
}
}
loo(1);
A function that calls itself is called a recursive function. In some ways, recursion is analogous to a loop. Both execute the same code multiple times, and both require a condition (to avoid an infinite loop, or rather, infinite recursion in this case). For example, the following loop:
Nothing. If you run this code, it actually does work correctly. It just doesn't output anything. If you want to see some output from it, try running it like this:
function loo(x)
{
if (x >= 10)
{
console.log("finished")
return;
}
console.log("x=" + x);
loo(x+1);
}
loo(1);
let trace your code :
function loo(x) {
if (x >= 10) {
return;
loo(x+1)
}
}
loo(1);
the function never works because if block always return noting so change the some statement of code like this :
function loo(x) {
if (x >= 10) {
return;
}
loo(x+1);
}
then call :
loo(1);

Maximum call stack size exceeded in recursive function

I have a recursive function which bubblesorts through an array in Javascript. The function calls on itself, which results in it exceeding the stack size of the browser and returning the error:
RangeError: Maximum call stack size exceeded
I understand the problem, and I've tried to wrapping the line which calls itself with setTimeout. This works, but, even if I set the time to 1ms, the sorting is significantly slower than if the setTimeout didn't exist.
Here's the function:
var pieces = [........]; // jumbled array
bubbleSort(0);
function bubbleSort(a) {
if (a < bars-1) {
onBar = a;
} else {
onBar = 0;
}
if (pieces[onBar] < pieces[onBar + 1]) {
// Correct order
bubbleSort(onBar + 1);
} else {
// Incorrect order
var p1 = pieces[onBar];
var p2 = pieces[onBar + 1];
pieces[onBar] = p2;
pieces[onBar + 1] = p1;
bubbleSort(onBar + 1);
}
}
For some strange reason, if I wrap one of the call lines in a setTimeout and leave the other untouched the function runs without any errors, but as soon as I leave both unwrapped it returns an error.
Thanks for your time. Any help appreciated.
You need a branch where you return without ever calling bubbleSort.

How to use timer in d3 V3?

I have a function triggerWave() which makes the points on the canvas animate in the wave form. I am using d3.ease('quad-in') for easing and I would like to use d3.timer() to make the triggerWave() function call over 200ms timeframe. I am out of luck in finding the tutorials or examples on d3.timer.
triggerWave() {
//function logic
let count = 0;
let xScale = d3.scale.linear().range([1,2]); // want the value to change from 1 to 2.
let zScale = d3.scale.linear().domain([0, 200]); // 200 ms.
let value = xScale(d3.ease('quad-in')(zScale(count)));
if(count < 200){
count++;
d3.timer(() => triggerWave());
} else {
// do something
}
this.wave.next({currentFrame: value});
}
When I call d3.timer() as above, the triggerWave() function gets called infinite times and never stops. I want to manipulate or control the time. In my case, I want the timer() to be triggered for 200ms.
How can I understand how to use the d3.timer() function?
(EDIT: I totally and completely missed the huge, big "V3" which is right there, in the title of the question. Sorry. I'll keep this answer here as reference for v4 users)
Since you are calling triggerWave inside the triggerWave function itself, you don't need d3.timer, but d3.timeout instead. According to the API, d3.timeout:
Like timer, except the timer automatically stops on its first callback. A suitable replacement for setTimeout that is guaranteed to not run in the background. The callback is passed the elapsed time.
Also, pay attention to the fact that you are reseting count every time the function runs, which will not work. Set its initial value outside the function.
Here is a demo with those changes. I'm calling the function every 200 ms, until count gets to 50:
var p = d3.select("p")
var count = 0;
triggerWave();
function triggerWave() {
p.html("Count is " + count)
if (count < 50) {
count++;
d3.timeout(triggerWave, 200)
} else {
return
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<p></p>
You can also keep track of the total elapsed time, using the argument passed to triggerWave by d3.timeout:
var p = d3.select("p")
var count = 0;
var elapsed = 0;
var format = d3.format(".2")
triggerWave();
function triggerWave(t) {
elapsed = t ? elapsed + t : elapsed;
p.html("Count is " + count + ", and the elapsed time is " + format(elapsed/1000) + " seconds")
if (count < 50) {
count++;
d3.timeout(triggerWave, 200)
} else {
return
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<p></p>
Since you are using D3 v3, and as there is no d3.timeout in v3, you can do the same approach using vanilla JavaScript: setTimeout.
Here is a demo:
var p = d3.select("p")
var count = 0;
triggerWave();
function triggerWave() {
p.html("Count is " + count)
if (count < 50) {
count++;
setTimeout(triggerWave, 200)
} else {
return
}
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<p></p>
In version 3, there is no d3.timer.stop() function. You have to return true after a certain period of time to stop the timer.
In Gerardo's answer, he explained fabulously how to use the d3 timeout which would be a valid solution to your problem i.e., to call the function over and over for a certain period of time. But looking at your comments on Gerardo's answer, I think you are looking for something else.
Here's what I came up with and I think this is what you are looking for:
You can create an another function called as activateTriggerWave() which will be invoked on the button click and inside this function, you can call your triggerWave() method using the d3 timer.
function activateTriggerWave() {
d3.timer(elapsed => {
this.triggerWave();
if(elapsed >= 200){
return true; // this will stop the d3 timer.
}
});
}
triggerWave() {
// here you can do whatever logic you want to implement.
}
I hope this helps.
I use d3.js v3, and the timer can be stopped by any user action. In the d3.js docs it is shown to use it as:
d3.timer(function(elapsed) {
console.log(elapsed);
return elapsed >= 1000;
});
I have few examples in which the animation is forever and there is no reason to set a limit on it. Checking the standalone d3.timer which comes with a stop(), I found that it behaves quite slow comparing it with the default timer included in the v3 toolset, probably for some version incompatibility.
The solution is use is to:
var timer_1_stop=false;
set it as global var accesible from the page scope. Then the timer:
const run=function(){
//...
d3.timer(function() {
voronoi = d3.geom.voronoi(points).map(function(cell) { return bounds.clip(cell); });
path.attr("d", function(point, i) { return line(resample(voronoi[i])); });
return timer_1_stop;
});
}
const stopVoro=function(){
timer_1_stop=true;
}
It allows to do:
class Menu extends React.Component {
render(){
return(<ul>
<li><span onClick={()=>{stopVoro()}}>StopVoro</span></li>
</ul>)
}
}

Javascript for loop, index variable in function

I'm trying to figure out how to generate functions inside for loop.
I have:
for (var i = fir_0_f.length - 1; i >= 0; i--){
var next = i+1;
var N = i;
// Attemps
//goal0_[i](next,N);
//eval('goal0_'+i+'('+next+', '+N+')');
};
Have done also some searching. [] expects a string, eval() is a B.A.D practice. Is there any other way?
How to set the timeout for each function later? So they would run sequentally?
Thanks a lot!
In JavaScript you could use function expressions to build an array of functions:
var goals = [];
goals.push((function (param1, param2) {
// your code for the first function
}));
goals.push((function (param1, param2) {
// your code for the second function
}));
// ... etc
Then in your for loop, you can simply reference your functions as elements of an array:
goals[i](next, N);
UPDATE:
To call your functions with a delay between each other, you'd have to change your loop logic. Instead of using a for loop, call the first function immediately, and then after it runs, make it call the second one using a setTimeout().
for (var i = fir_0_f.length - 1; i >= 0; i--){
var next = i+1;
var N = i;
setTimeout('goal0_'+i+'('+next+','+N+')', 0);
}
Note: errors thrown by goal0_i won't be caught by the loop.
I've noticed this behavior in Firefox.
That means that the following won't work as you expected:
try{
setTimeout(function_throwing_error, 0);
}
catch(e){
alert("I kill you!");
}
For global functions, you can just do:
window['goal0_'+i](next, N);

Categories