Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid a recursive stack overflow in promise-based loop? [duplicate]

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?

like image 251
Sean Avatar asked Sep 29 '22 01:09

Sean


1 Answers

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.

like image 66
Dark Falcon Avatar answered Oct 06 '22 20:10

Dark Falcon