jsedano
jsedano

Reputation: 4216

How does Javascript manages recursive calls?

I was goofing around with JavaScript, and a notice a strange behavior (strange for me at least. . .)

So I did a SSCCE here it goes:

I have a div named "myDiv"

function changeText(text){
    document.getElementById("myDiv").innerHTML=text;
}

function recursiveCall(counter){
    if(counter){
        setTimeout(function(){
            recursiveCall(--counter);
            changeText(counter);
        },750);
    }
}

recursiveCall(10);

Live example: http://jsfiddle.net/T645X/

So I'm changing the text on the div, and what happens is that the text goes from 9 to 0, while I thought that it was suppose to go from 0 to 9, since the recursive changeText(counter); call is before calling the method that actually changes the text.

Upvotes: 8

Views: 280

Answers (3)

Ja͢ck
Ja͢ck

Reputation: 173572

One thing to understand is that it's not recursion in the first place; if your function didn't have a proper exit clause, this could go on forever without running into a blown stack.

The reason is that any function you pass to setTimeout() is run outside of the current execution context; in other words, the code "breaks out" of your function.

If you want a recursive call with 750ms in between them, you could do something like this:

function recursiveCall(counter, fn)
{
    if (counter) {
        recursiveCall(--counter, function() {
            changeText(counter);
            setTimeout(fn, 750);
        });
    } else if (fn) {
        fn(); // start chain backwards
    }
}

It creates a chain of callbacks when it recurses and the exit clause sets the whole chain motion, backwards :)

Upvotes: 2

Alnitak
Alnitak

Reputation: 339816

Strictly speaking there is no recursion here.

The call to setTimeout just adds a callback to a list of scheduled timer events.

Much of the time, your browser is just sat there waiting for events, it processes those (i.e. runs your event handlers) and then goes back to waiting for events.

So in this case what you're doing is:

   recursiveCall(10)
   timer event and callback added to the queue
   function exits

... waits 750 ms ...

   timer event fires, callback pulled from the queue and invoked
      -> recursiveCall(9) invoked
        ->  timer event and callback added to the queue
      -> changeText(9) invoked
   callback function exits

... waits 750 ms ...

   timer event fires, callback pulled from the queue and invoked
      -> recursiveCall(8) invoked
        ->  timer event and callback added to the queue
      -> changeText(8) invoked
   callback function exits

and so on...

I call this pseudo-recursion, because although it looks somewhat like classical recursion, each invocation starts at the same "stack frame", i.e. if you asked for a stack trace there would typically only be one (or in your case sometimes two) instance of recursiveCall present at a time.

Upvotes: 4

Benjamin Gruenbaum
Benjamin Gruenbaum

Reputation: 276306

The function contains a timeout which is asynchronous.

setTimeout(function(){
    recursiveCall(--counter);// calls the next function, which will call the next 
                             // and print in a timeout
    changeText(counter);  // print
},750);

The text is changed before the recursive call hits the timeout.

If you'd like to, you can move the print call from outside the timeout, which would result in the expected behavior as such:

function recursiveCall(counter){
    if(counter){
        recursiveCall(--counter);            
        setTimeout(function(){
            changeText(counter);
        },750);
    }
}

(Although, note that here the printing is not timed apart, and we're relying somewhat on undefined behavior assuming it'd print first just because we put the timer first)

If you would like it to still print in delays, you can tell the function it's done. Recursion will still be done initially, but each level will tell the level above it that it is done:

function recursiveCall(counter,done){
    if(counter){
        // note how recursion is done before the timeouts
        recursiveCall(counter-1,function(){ //note the function
            setTimeout(function(){          //When I'm done, change the text and let the 
                changeText(counter-1);      //next one know it's its turn.
                done(); // notify the next in line.
            },750);
        });
    }else{
        done(); //If I'm the end condition, start working.
    }
}

Here is a fiddle implementing this.

Upvotes: 8

Related Questions