11

I was asked (by a friend) to build a timer (infinite one which writes a line every second), but without setInterval.

I solved it with :

var i = 0;

    function k(myId, cb)
    {
        setTimeout(function ()
        {
            console.log(myId);
            cb();
        }, 1000);
    }

    function go()
    {
        i++;
        k(i, go);
    }

    go();

And it is working.

The problem is that I'm afraid there's gonna be a memory pressure. It actually creates a recursion and after a while (week or something) - the process will consume much memory. (the stack is never deallocated)

How can I change my code in order not to be much memory consume?

Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • _"I'm afraid there's gonna be a memory pressure"_ - You're worried that there might be, or you've tested it and there is? You mention running it for a week - did you actually do that and have a problem? As mentioned in the answers below this isn't recursion. Also, why the triple-function setup with `go()` and `k()` and the anonymous function? You could do the `console.log()` within `go()` and then do `setTimeout(go,1000)`. – nnnnnn Nov 22 '12 at 06:30

3 Answers3

14

It's not recursion

It may look like recursion, but setTimeout does not create recursion.

The way setTimeout works is that it returns immediately. So the call to k ends immediately with its stack deallocated.

When the timeout actually happens and the call to go happens again it is not from the point of the previous call to k but from the global scope*.

* Note: I'm not using the strict meaning of scope as defined in ECMAScript spec here. What I mean is the call to k will be made as if you have written it in a plain <script></script> tag: that is to say, outside of any other function calls.

Regarding your concern over the closure

In your specific case, there is very little that's actually enclosed in the closure created by the k function. The only significant closure is the reference to the arguments cb and myId. And even then it only lasts for approximately one second:

 #1   function k(myId, cb) {
 #2        setTimeout(function(){
 #3            console.log(myId); // there is a closure here to myId
 #4            cb();              // and another one for cb
 #5
             /* But at this point in the function, setTimeout ends
             * and as the function returns, there are no remaining
             * references to either "cb" or "myId" accessible
             * anywhere else. Which means that the GC can immediately
             * free them (though in reality the GC may run a bit later)
             */
  #6       }, 1000); // So one second is roughly the longest the closure lasts
    }

Could be simpler

I should note that your code is fairly convoluted. It can be written simpler, and without using closures at all (minus the global variable i) if you simply write it like this:

// Simpler, does exactly the same thing:
var i = 0;
function go () {
    console.log(i);
    i++;
    setTimeout(go, 1000); // callback
}
go();
Mzachen
  • 149
  • 2
  • 3
  • 14
slebetman
  • 109,858
  • 19
  • 140
  • 171
  • 1
    "scope" pertains to a function's lexical context, not to the call stack. Better wording might be, "When the timeout actually happens and the call to `go` happens again, it begins with a new empty stack. It does not continue to add to the previous stack." – Lee Nov 22 '12 at 06:28
  • _"but from the global scope"_ - That's not quite right. What about the closure from the previous call to `k`? The anonymous function passed to `setTimeout()` uses one of the parameters from `k()`, so even though `k()` has completed executing pieces of it still hang around at least for a while... – nnnnnn Nov 22 '12 at 06:34
  • @nnnnnn can you elaborate on this ? – Royi Namir Nov 22 '12 at 07:53
  • 1
    @Lee: Clarified what I meant. Didn't use your wording because although the wording is technically wrong, I find that more people grasp the concept more quickly this way. – slebetman Nov 22 '12 at 08:48
  • @RoyiNamir: Read my answer to this: http://stackoverflow.com/questions/13301050/js-global-variable-not-being-set-on-first-iteration/13301270#13301270 . It's about XMLHttpRequest instead of setTimeout but there is a nice ASCII ART diagram that explains how the event loop works in browsers and that applies to setTimeout as well. – slebetman Nov 22 '12 at 09:08
  • (added line numbers - and here is the question :-)) ----What is happenning with the function in line #2 ? when js sees setTimeOut - it actually run it + wait 1 sec and THEN IT REGISTER THE CODE IN A CALLBACK FUNCTION ? – Royi Namir Nov 22 '12 at 09:38
  • @RoyiNamir: No. setTimeout returns immediately without executing anything. It schedules the code passed to it (either a function reference or a string) to be executed at a later time. The browser then processes the rest of the javascript code (without executing the code in setTimeout) and when there is nothing else to process it enters the event loop. In the event loop, after one second (and many, many events like mouse movements and network activity) the setTimeout will expire and only then will your code execute. – slebetman Nov 22 '12 at 09:43
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/19929/discussion-between-royi-namir-and-slebetman) – Royi Namir Nov 22 '12 at 09:45
7

This line is false:

It actually creates a recursion and after a while (week or something) - the process will consume much memory. ( the stack is never deallocated)

It does not create recursion, because the function exits completely and is then called again.

Recursion stacks on top of each other

function a() {a()}; // function calls itself until a stack overflow.

The stack looks like this

a()
  a()
    a()
      a() ... until a crash.

With setTimeout, you execute a function. That function sets up a event for the function to run again -- but here is the important difference: The function exits, completely, and is gone[1]. THEN it is called again.

Execution wise, it isn't much different from doing this:

function a() {console.log("I am called");}

a(); // Call the function;
a(); // Call the function again
a(); // Call the function again

setTimeout just gives the browser a chance to 'breath' if you will. A chance for the screen to update, other events to process. It doesn't, to use the right terminology, block the browser.

Jeremy J Starcher
  • 23,369
  • 6
  • 54
  • 74
  • Note that the function expression in the OPs `setTimeout` call creates a closure to the outer function's execution context, and explicitly to `cb`, so there is a scope stack being formed. It may be optimised away since the only needed closure is to the immediate outer and global contexts, but that is implementation dependent and perhaps not reliable. In any case, you're right that it shouldn't create any issues. – RobG Nov 22 '12 at 06:37
  • 1
    Nice answer. I like the `a();a();a()` example – slebetman Nov 22 '12 at 08:49
  • @RoyiNamir—there's a closure between the anonymous function passed to `setTimeout`, the outer `k` function and the global object. But when the anonymous function is called and finishes executing, it is (or should be) available for garbage collection. A new closure is formed with the new function in the new `setTimeout`, but it does not involve the previous closure, it's a completely new execution context. – RobG Nov 22 '12 at 09:45
  • @RobG can you join please http://chat.stackoverflow.com/rooms/19929/discussion-between-royi-namir-and-slebetman ? – Royi Namir Nov 22 '12 at 09:51
  • @RobG -- absolutely agree on the closer issue. I ignored it in my answer, however, out of simplicity. – Jeremy J Starcher Nov 22 '12 at 14:56
2

This does not create a memory leak.

In fact, it's a pretty commonly used concept. Usually it appears in this form:

setTimeout(function next() {

    // Do something...

    // Have the function set another timeout to call itself later.
    setTimeout(next, 10);

}, 10);

When you want to check for something often (here every 10 milliseconds), it can be better to use this pattern instead of setInterval because it can improve your page performance. For instance, if your function takes more than 10 milliseconds to execute, and you use setInterval(f, 10) then it will continually be being called. However, if you use the setTimeout pattern above, it will at least make sure the processor gets a 10 millisecond break between each call, no matter how long your function takes to execute.

See this video (starting at 7:46) from Paul Irish for more information about this pattern.

Nathan Wall
  • 10,530
  • 4
  • 24
  • 47