Vera
Vera

Reputation: 293

recursion in javascript: don't quite understand

I'm practicing using recursion, and there's something I don't quite get. For example, I wrote this simple countdown function, which is supposed to wait until a second elapsed until counting down to the next second.

I first wrote it like so:

function countdown(sec) {
  console.warn(sec);
  if(sec > 0) {
     sec--;
     setTimeout(countdown(sec), 1000);
  }
}

It does not wait one second between each log. This works:

function countdown(sec){
   console.warn(sec);
   setTimeout(function() {
       sec--;
       if (sec > 0) {
          countdown(sec);
       }
   }, 1000);
};

I don't really understand what's wrong with the first approach. I guess it's something with setTimeout that I don't quite understand, and scoping..?

Thanks in advance for any explanations.

--- edited & working, thanks guys! ---

I didn't know about bind being used as a shorthand.

function countdown(sec) {
   console.warn(sec);
   if (sec > 0) {
       sec--;
       setTimeout(countdown.bind(null, sec), 1000);
   }
}

Upvotes: 0

Views: 92

Answers (2)

Mulan
Mulan

Reputation: 135217

JosephNields is correct for why your code was not working, but I'd also like to stress that recursion generally doesn't involve mutating state values, ie sec--

Instead, just pass sec - 1 as the next value for countdown. In other words, there's no gain in setting sec to a smaller number, just recurse with the smaller number

var countdown = function (sec) {
  console.log(sec)
  if (sec > 0)
    setTimeout(countdown, 1000, sec - 1)
}

countdown(10)

Also, wouldn't it be great to know when a timer is done? This example shows passing around another value as you recurse.

var countdown = function (sec, done) {
  console.log(sec)
  if (sec > 0)
    setTimeout(countdown, 1000, sec - 1, done)
  else
    done()
}

countdown(5, function () {
  console.log('timer is all done!')
})

Upvotes: 1

Joseph Nields
Joseph Nields

Reputation: 5661

the first argument to setTimeout should be a function.

When you call setTimeout(countdown(sec),1000), it gets evaluated to setTimeout(undefined, 1000) because countdown(sec) does not return a value.

Your second method works, but as shorthand you could also do this: setTimout(coundown.bind(null, sec), 1000). This creates a function that will execute countdown(sec) when called, which is basically equivalent to the following:

setTimeout(
    function() {
        countdown(sec);
    }, 
    1000
);

More info here: Use of the JavaScript 'bind' method

Upvotes: 0

Related Questions