8

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.

3 Answers 3

8

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.

Sign up to request clarification or add additional context in comments.

10 Comments

@anakata I'd just like you to know that the reason you got an answer so fast (and an upvote) is because you phrased your question very well and it contained a simple reproducible example. Props for reading the manual and glad I could help :)
@elclanrs passing additional parameters to setTimeout is non-standard
@anakata On another note, if you'd like it to count the other way around you can use a callback.
I just realized that for using recursive calls, plus setTimeout, you need to think in 3 levels, normal execution+the stack frame of the calls+the asynchronous calls
@BenjaminGruenbaum: Nice info, I guess is IE that holds us back, what a surprise! Maybe it works in "modern" IE's though.
|
4

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.

4 Comments

I this anything like tail recursion? (but in separate threads?)
Strictly strictly speaking, this is absolutely recursion: the function recursiveCall refers to itself in its own definition. I guess what you mean is, since it never invokes itself, there are no recursive calls.
@ruakh yes, although it refers to itself, it never (directly) calls itself. That's why I call is pseudo-recursion.
@anakata no, not really - tail recursion is when the recursive call to itself is the very last thing a function does. It's a good indicator that the function could be replaced with an iterative implementation instead.
2

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 :)

1 Comment

@anakata You mean the fn argument is a callback? Yes, you could call it that :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.