Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 (https://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()?

like image 783
BaldEagle Avatar asked Aug 05 '16 21:08

BaldEagle


People also ask

Are Promises run before setTimeout?

The reason the promise is executing before your timeout is that the promise isn't actually waiting for anything so it resolved right away.

What is long polling in JavaScript?

Long polling is the simplest way of having persistent connection with server, that doesn't use any specific protocol like WebSocket or Server Side Events. Being very easy to implement, it's also good enough in a lot of cases.

Is there any limit for Promise all?

Assuming we have the processing power and that our promises can run in parallel, there is a hard limit of just over 2 million promises.

What is polling in frontend?

Polling is a technique for making requests in a non-blocking manner. It is particularly useful for applications that need to make requests to services which take a long time to process the request. Let's say we have a client and a server.


2 Answers

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.

like image 63
jfriend00 Avatar answered Sep 22 '22 04:09

jfriend00


Since you said you found polling functions that have callbacks, this basically boils down to "How do I promisify something?"

Use BluebirdJS's Promisify:

var poleYouFoundThatHasCallback = require('somePollLibrary');
var Promise = require('bluebird');
var poll = Promise.Promisify(poleYouFoundThatHasCallback);

poll.then((res) => { 
//dostuff with res here

}).catch(err => console.log(err))

You can also throw a timeout on it for shits and giggles.

Here's and example from the docs:

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require('fs'));
fs.readFileAsync("huge-file.txt").timeout(100).then(function(fileContents) {

}).catch(Promise.TimeoutError, function(e) {
    console.log("could not read file within 100ms");
});
like image 29
Patrick Motard Avatar answered Sep 18 '22 04:09

Patrick Motard