BaldEagle BaldEagle - 3 months ago 7
Javascript Question

How can I keep `.then()` alive long enough for a polling function with native promises?

Summary:

poll()
functions with callbacks are available; I haven't found any using native promises. I've tried to adapt some without success. The problem I haven't solved yet is that when the first instance of the function called by setTimeout ends without any return, the
.then()
listening for it sees the termination as a
false
and a
reject()
.
then()
terminates and doesn't listen for later returns.

Question: How best to help the
.then()
function stick around for later returns with
resolve()
or
reject()
?

The rest of this post is detail. Read what helps.

Available poll functions: I like (http://stackoverflow.com/users/1249219/om-shankar) Om Shankar's response in Calling a function every 60 seconds. David Walsh's poll() is very similar (at https://davidwalsh.name/essential-javascript-functions). Both use callbacks and work well. I found
poll in javascript
which includes a
poll()
using
bluebird
-only promises.

Here's my attempt at implementing with native promises.

/**
* poll - checks repeatedly whether a condition exists. When the condition
* exists, returns a resolved standard promise. When it has checked
* long enough, returns a rejected standard promise.
* @param {function} fn - a caller-supplied synchronous function that
* detects a condition in the environment. Returns true if the
* condition exists; otherwise false.
* @param {number} timeout - maximum number of milliseconds
* the caller wants to check param fn();
* reject() the promise at the expiration of param timeout.
* @param {number} interval - minimum number of milliseconds between
* calls to param fn(); resolve() the promise when param fn() first
* reports true.
* @return {promise} - resolved when param fn() returns true;
* rejected if param timeout expires without param fn() returning true
*/
function poll(fn, timeout, interval) {
let endTime = Number(new Date()) + (timeout || 2000)
interval = interval || 250
return Promise.resolve *2
.then(() => { *3
(function p(fn, endTime, interval) {
if (fn()) { return Promise.resolve("Condition is satisfied.") } *4
else {
if (Number(new Date()) <= endTime) {) *5
window.setTimout(p, interval, fn, endTime, interval) *6
}
else {
return Promise.reject("Past endTime; condition not satisfied")
}
}
}()) *7
}) *8
}


Expected usage:

function waitIsOver() { return (<desired condition exists>) }
poll(waitIsOver, 2000, 250) *1


The way I think this is running (please correct me if I'm wrong): After the call to
poll()
at *1, we quickly return a pending promise at *2 so that
poll()
knows to wait. Then, we call that promise's
then()
function at *3. Function
p()
starts. If
fn()
(known outside
p()
as
waitIsOver()
) returns true at *4, we're good: We return
resolve()
and
poll()
at *1 gets the settled promise it seeks.

Then the bad part: If
fn()
returns false at *4 and we're inside
endTime
at *5 (which is likely; the first call is unlikely to occur after
endTime
), we use
setTimeout()
at *6 to ask JS to make a note in the stack to instantiate another
p()
after
interval
time. After that, the first instance of
p()
terminates at *7. At *8,
then()
knows that
p()
terminated without returning anything and interprets the condition as returning
false
and
reject()
; with
reject()
, the promise is settled and can never change. However, after expiration of
interval
, a successor instance of
p()
fires up. Anything it returns is lost; the promise is settled and
then()
has terminated after sending execution on an unwanted path.

How do I convert an existing callback API to promises? recommends an approach with a Promise constructor,
resolve()
calling
callback()
, and
reject()
calling
errback
. I tried the technique, but I ran into the same problem of the
then()
function ending before I want it to. I haven't yet figured out how to make
then()
as patient in waiting as a callback function.

That sets up the question. Again:

Question: How best to help the
.then()
function stick around for later returns from
resolve()
or
reject()
?

Answer

How best to help the .then() function stick around for later returns from resolve() or reject()

A .then() handler is called when the underlying promise is resolved or rejected. It's not called before that, ever. So, if you want to delay when the .then() handler is called, then you delay resolving or rejecting the underlying promise until the appropriate time.

As you can tell from my comments, it is hard to tell exactly what you're trying to accomplish because you don't just describe a straightforward objective you are trying to accomplish.

Given that, here's my guess at what you're trying to accomplish. A clear question could have receive an answer like this in a few minutes.

If you just want to repeatedly poll your function until it returns a truthy value or until the timeout time hits, you can do this with standard ES6 promies:

function poll(fn, timeout, interval) {
    return new Promise(function(resolve, reject) {
        // set timeout timer
        var timeoutTimer = setTimeout(function() {
            clearInterval(intervalTimer);
            reject("Past endTime; condition not satisfied");
        }, timeout);

        // set polling timer
        var intervalTimer = setInterval(function() {
            if (fn()) {
                clearTimeout(timeoutTimer);
                clearInterval(intervalTimer);
                resolve("Condition is satisfied");
            }
        }, interval);
    });
}

poll(yourFounction, 5000, 100).then(function(result) {
    // succeeded here
}).catch(function(err) {
    // timed out here
})

Or, with the Bluebird promise library, you can use its .timeout() method to do this:

function poll(fn, timeout, interval) {
    return new Promise(function(resolve, reject) {
        // set polling timer
        var intervalTimer = setInterval(function() {
            if (fn()) {
                clearInterval(intervalTimer);
                resolve("Condition is satisfied");
            }
        }, interval);
    }).timeout(timeout, "Past endTime; condition not satisfied");
}

poll(yourFounction, 5000, 100).then(function(result) {
    // succeeded here
}).catch(function(err) {
    // timed out here
})

Notice that both these schemes return a promise and then when the poll() function is done, they either call resolve or reject on that new promise and that will then trigger any .then() handlers to get called.


P.S. I should add that this all assumes your fn() is a synchronous function that returns a truthy or falsey value (which is what your code seems to presume). If your fn() is actually an asynchronous function with a callback or a promise, then that needs to be factored into the design. You would have to show us what the calling convention is for the function before we could write code to use that correctly.

Comments