0

In order to have a single instance of a function running at all the time, will the following way be problematic after some point?

const Q = require('q');

(function main() {
        Q.fcall(somePromiseFunction).then(_ => {
                main();
        });
})();

I am wondering if after a while the recursive memory stack will overflow and the program will terminate or not?

omid.n
  • 491
  • 5
  • 22

1 Answers1

3

Surprisingly, no! I believe your code should run fine.

Asynchronous operations in JavaScript, such as setTimeout(), XMLHttpRequest, and Promise utilize something called the Event Loop to execute outside of the normal flow of the program. Effectively, when something asynchronous is going to happen, it gets added to the 'queue' (which is entirely separate from the stack). Once the stack has emptied, the Event Loop will start processing these queued messages, executing their associated functions one by one.

Pretty much everything asynchronous in JavaScript works this way, and that includes the Q promises library. So in the case of your example, this is a (very) simplified explanation of what's happening:

  • main() is called, creating a new stack frame.
  • Q.fcall(somePromiseFunction) is called, creating a second stack frame (and presumably a third, when somePromiseFunction is called. This sets off the asynchronous operation in the background, with the function passed to then set as the callback.
  • Q.fcall(somePromiseFunction) has now returned, so those stack frames are cleared.
  • main() has also reached the end, so that gets cleared too - we're back to an empty stack.
  • Once the asynchronous operation is complete, a message gets pushed onto the event queue, with your then callback associated to it.
  • The stack is empty, so the event loop starts processing.
  • Your callback gets executed.

The important thing to notice here is that your code does not recurse! Your then callback only ever gets called from the event queue, not by main(), so the stack is able to clear without problems between iterations.

I would recommend watching the talk 'What the heck is the event loop anyway?' for more information on how this all works - the way they explained it was what made it finally click for me.

EDIT: In response to your questions in the comments, I'll clarify a few things. A recursive function, in its most simple form, looks like this:

function recursive() {
    recursive();
}

Calling a function allocates a stack frame, which is cleared when it returns. However, without some sort of way of breaking out of the loop, a recursive function such as this one is never going to return - it will just keep going, endlessly allocating stack frames until you get a stack overflow error. Not that this means you should never use recursion in JavaScript - it can be quite useful sometimes! But you need to be aware that if it recurses too much without returning, you're going to hit the top of the stack.

In answer to your other question - the event loop/your callback doesn't wait for main() in particular to end, it just waits for the stack to clear. Here's a simplified visualization of what the stack does in your program:

[]
[main]
[main, fcall]
[main, fcall, somePromiseFunction]
[main, fcall]
[main]
[main, then]
[main]
[]                                 // Main returned, so the stack is clear
[yourCallback]                     // The event loop kicks in, runs your callback
[yourCallback, main]
...                                // Above steps repeat
[yourCallback]                     // Main returned
[]                                 // Stack clears, so the event loop kicks in again

Compare this to my endlessly recurring function:

[]
[recursive]
[recursive, recursive]            // Nothing stops the recursion,
[recursive, recursive, recursive] // so the stack is never going to clear!
Joe Clay
  • 33,401
  • 4
  • 85
  • 85
  • Yeah it seem to be working for a long time. But I'm dubious about how does it figure out on the 5th line that the main() has reached to the end before actually running main() again. If the stack frame is always removed then how does the recursive functions actually work in JavaScript. BTW, Thanks for the informative answer. Appreciated! – omid.n Jul 12 '16 at 10:57
  • 1
    @omid.n: I edited my answer to clarify some of the stuff you asked. Let me know if you need any more info :) – Joe Clay Jul 12 '16 at 11:12
  • This is almost correct, but its worth noting that [javascript doesn't support tail call optimization](https://stackoverflow.com/questions/37224520/are-functions-in-javascript-tail-call-optimized) meaning recursive functions that return do not clear their stack frame. `Avoid writing recursive functions in any language that is not Tail Call Optimized`. – Richard Tyler Miles Dec 13 '22 at 16:31