5
var recurse = function(steps, data, delay) {
    if(steps == 0) {
        console.log(data.length)
    } else {
        setTimeout(function(){
            recurse(steps - 1, data, delay);
        }, delay);
    }
};

var myData = "abc";
recurse(8000, myData, 1);

What troubles me with this code is that I'm passing a string on 8000 times. Does this result in any kind of memory problem?

Also, If I run this code with node.js, it prints immediately, which is not what I would expect.

Steinbitglis
  • 2,482
  • 2
  • 27
  • 40
  • 2
    Setting timeout as low as 1 (actually less than 30 or so) causes the browsers to just execute the function in the next iteration of the main loop. – Emil Ivanov Aug 11 '11 at 12:57
  • 1
    @Emil: It varies by implementation. Some implementations won't let you set the delay to less than 5 or 10, even if they loop earlier than that. Or so I'm told, I haven't actually gone and looked at the source myself. – T.J. Crowder Aug 11 '11 at 13:06

4 Answers4

4

If you're worried about the string being copied 8,000 times, don't be, there's only one copy of the string; what gets passed around is a reference.

The bigger question is whether the object created when you call a function (called the "variable binding object" of the "execution context") is retained, because you're creating a closure, and which has a reference to the variable object for the context and thus keeps it in memory as long as the closure is still referenced somewhere.

And the answer is: Yes, but only until the timer fires, because once it does nothing is referencing the closure anymore and so the garbage collector can reclaim them both. So you won't have 8,000 of them outstanding, just one or two. Of course, when and how the GC runs is up to the implementation.

Curiously, just earlier today we had another question on a very similar topic; see my answer there as well.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Edited for bad logic on my part. Is that technically a closure? I'm not sure how chained params are handled in a recursion scenario. Also, the internet can't seem to agree on what the definition of a closure is. I get the general idea of sticking your foot in the door of scope completion though. – Erik Reppen Aug 11 '11 at 13:33
  • @Erik: Yes, it is, technically, a closure (more: *[Closures are not complicated](http://blog.niftysnippets.org/2008/02/closures-are-not-complicated.html)*). :-) That uses the old 3rd edition spec terminology, but you can still follow it. In the wider world of computing (and math), "closure" is a general term for a function that has data intrinsically bound to it. In JavaScript, the data that gets bound is a reference to the variable binding object at the top of the scope chain where the function was created (which has a link to the VBO for the previous execution context, if any, etc.). – T.J. Crowder Aug 11 '11 at 13:40
2

It prints immediately because the program executes "immediately". On my Intel i5 machine, the whole operation takes 0.07s, according to time node test.js.

For the memory problems, and wether this is a "cheap infinite loop", you'll just have to experiment and measure.

If you want to create an asynchronous loop in node, you could use process.nextTick. It will be faster than setTimeout(func, 1).

August Lilleaas
  • 54,010
  • 13
  • 102
  • 111
2

In general Javascript does not support tail call optimization, so writing recursive code normally runs the risk of causing a stack overflow. If you use setTimeout like this, it effectively resets the call stack, so stack overflow is no longer a problem.

Performance will be the problem though, as each call to setTimeout generally takes a fair bit of time (around 10 ms), even if you set delay to 0.

Community
  • 1
  • 1
minimalis
  • 1,763
  • 13
  • 19
2

The '1' is 1 millisecond. It might as well be a for loop. 1 second is 1000. I recently wrote something similar checking on the progress of a batch of processes on the back end and set a delay of 500. Older browsers wouldn't see any real difference between 1 and about 15ms if I remember correctly. I think V8 might actually process faster than that.

I don't think garbage collection will be happening to any of the functions until the last iteration is complete but these newer generations of JS JIT compilers are a lot smarter than the ones I know more about so it's possible they'll see that nothing is really going on after the timeout and pull those params from memory.

Regardless, even if memory is reserved for every instance of those parameters, it would take a lot more than 8000 iterations to cause a problem.

One way to safeguard against potential problems with more memory intensive parameters is if you pass in an object with the params you want. Then I believe the params will just be a reference to a set place in memory.

So something like:

var recurseParams ={ steps:8000, data:"abc", delay:100 }  //outside of the function

//define the function

recurse(recurseParams);

//Then inside the function reference like this:

recurseParams.steps--
Erik Reppen
  • 4,605
  • 1
  • 22
  • 26