6

As a simple example program, I have a node script which pings a server continuously, and wish for this program to be ran for a long time.

The program is set up as a ping function which returns a promise object. The promise is resolved or rejected based on whether the ping worked or failed.

I wish to have this function running in a loop, so regardless of whether the ping is successful or not, the next ping is then fired after a certain amount of time after the previous request has been resolved.

The problem is not this task itself, but I'm concerned about my implementation. I believe it will cause a stack overflow eventually.

Here's some code to see what's happening:

function doPing(host) {
    // returns a promise object.
}

function doEvery(ms, callback, callbackArgs) {

    setTimeout(function() {

        callback.apply(null, callbackArgs)
            .always(function() {

                doEvery(ms, callback, callbackArgs);

            });

    }, ms);

}

doEvery(1000, doPing, [host]);

I've tried to limit the code just to reflect the scope of the following questions:

Will this eventually cause a stack overflow? Is there a pattern which prevents overflows for callback-based loops while using promises?

Sean
  • 2,278
  • 1
  • 24
  • 45
  • Apologies for the duplicate question - the one in question didn't appear in an initial search, or when writing up the question. I believe this question is more specific. The question linked is opinion based while I've tried to keep this as objective and specific to promise-based loops as possible. – Sean May 05 '15 at 14:01

1 Answers1

4

No stack overflow here. setTimeout is an asynchronous function: it schedules the function to be run but does not invoke it immediately. Since the repeated call to doEvery is inside the callback for setTimeout, this will ensure that it does not overflow.

Here is an example of what the deepest stack might look like at various points:

  • When scheduling the first ping: [global scope] -> doEvery -> setTimeout
  • When running the first ping: [event loop] -> [handle timer] -> [closure #1 in doEvery] -> callback.apply -> doPing
  • When the first response is received: [event loop] -> [handle network] -> promise.resolve -> [closure #2 in doEvery] -> doEvery -> setTimeout
  • When the second timeout expires: [event loop] -> [handle timer] -> [closure #1 in doEvery] -> callback.apply -> doPing

So as you can see, every time you wait on a promise or a timeout, control is returned to the event loop. When the event (such as the timeout is reached or a ping response is received), the event loop then invokes the callback registered for that event.

Dark Falcon
  • 43,592
  • 5
  • 83
  • 98
  • Thanks. I was concerned that asynchronous functions would still cause a stack to grow. – Sean May 05 '15 at 13:58