0

The below non-functional example should explain what I'm trying to do, I just don't understand the pattern I need to use to accomplish it. I tried googling to understand polling and deferred, but I couldn't find anything I could understand.

I have a function which polls an API, and I want to wait for that polling to return an expected result (waiting for the endpoint to indicate something has changed) before continuing with my main function. What am I doing wrong?

Edit: I should add that what SEEMS to go wrong with the code below is that even though deferred.resolve() eventually gets called, it seems it isn't the same deferred that got returned, so the when never gets activated in main(). I'm guessing it has to do with the timeout, meaning deferred get clobbered on the first repeat. That is my assumption, anyways.

function pollAPI() {
     var deferred = $.Deferred();

     $.ajax({                                                                  
       url: url,                                       
       contentType: 'application/JSON',                                      
       method: 'GET'                                                         
     }).done(function(data){                                                                                                                              
         if (!desiredResult) {                                                 
             setTimeout(function() {                                         
               pollAPI();                            
             }, 1000);                                                       
         } else {                                                            
           deferred.resolve();
         }                                                                                                                                           
     }).error(deferred.reject());                                            

     return deferred.promise();
 }

function main() {
    $.when(pollAPI()).then(function() {
        // do something now that the API has returned the expected result
    });
fildred13
  • 2,280
  • 8
  • 28
  • 52
  • each call to pollAPI creates a new deferred, nothing gets clobbered - try `deferred.resolve(pollAPI())` in the setTimeout – Jaromanda X Sep 15 '16 at 01:37
  • Wouldn't that cause the first deferred to resolve, thus causing the `when` to trigger even though it is not ready? – fildred13 Sep 15 '16 at 01:41
  • yes, it will resolve to an as yet unresolved promise - so, should not be an issue - see section 2.3 in [Promise/A+ specification](https://promisesaplus.com/) - jquery possibly follows this spec, but there are significant departures from the Promise/A+ spec in some earlier versions of jQuery – Jaromanda X Sep 15 '16 at 01:45
  • Well I gave it a shot, and while you're right that it didn't cause an issue, it also did not trigger the `when` in `main()` when the desiredResult finally came back as expected, so I seem to still be in the same boat. – fildred13 Sep 15 '16 at 01:48
  • I've never trusted jQuery's so called "Deferred" anyway - so it doesn't surprise me – Jaromanda X Sep 15 '16 at 01:49
  • see if [this](https://jsfiddle.net/t2oe0f97/1/) is of any use (that code wont do anything, obviously, just putting it there so you can copy) – Jaromanda X Sep 15 '16 at 01:59

1 Answers1

3

You can use chaining of subsequent calls to the pollAPI() function to create a single promise that has others chained onto it. That would work like this:

// utility function to create a promise that is resolved after a delay
$.promiseDelay = function(t) {
    return $.Deferred(function(def) {
        setTimeout(def.resolve, t);
    }).promise();
}

function pollAPI() {
    return $.ajax({
        url: url,
        contentType: 'application/JSON',
        method: 'GET'
    }).then(function(data) {
        // some logic here to test if we have desired result
        if (!desiredResult) {
            // chain the next promise onto it after a delay
            return $.promiseDelay(1000).then(pollAPI);
        } else {
            // return resolved value
            return someValue;
        }
    });
}

function main() {
    pollAPI().then(function(result) {
        // got desired result here
    }, function(err) {
        // ended with an error here
    });
}

This has the following benefits:

  1. No unnecessary promise is created to try to surround the ajax call that already has a promise. This avoids one of the common promise anti-patterns.
  2. Subsequent calls to the API just chain to the original promise.
  3. There is no need to use $.when() when you just have a single promise. You can just use .then() directly on it.
  4. All errors automatically percolate back to the original promise.
  5. This uses the ES6-standard .then() (which actually becomes more legitimately standard in jQuery 3.x - though it works in jQuery 1.x and 2.x with its own non-standard quirks) which makes this logic more compatible with other promise-producing async functions.

Also, a number of other retry ideas here: Promise Retry Design Patterns

Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • This is more or less working, and I appreciate the references. I am working through them to better understand the pattern. The only part I'm still confused about is the `return someValue`. If I set someValue to 'foobar' before the return, and then `console.log(result)`, then `undefined` is printed to the console. Am I misundertanding something about returning values through the the chain back to main()? – fildred13 Sep 15 '16 at 02:50
  • @fildred13 - You'd have to show me (perhaps as an addition onto your question) exactly what you're trying because `return someValue` (where you actually put a real value in place of `someValue`) will propagate back a resolved value to the original promise and will show up in `main()` as the `result` argument if you use code like I've shown. – jfriend00 Sep 15 '16 at 03:18
  • Oh I see what's wrong - for me, the `then` of pollAPI() isn't being called, even when i `return someValue`. Basically, the pollAPI() function finishes, but the `then` never gets called. Thoughts? – fildred13 Sep 15 '16 at 03:18
  • @fildred13 - Again, you'll have to show me what code you're actually using for me to know where you went wrong in the implementation. In my answer, you will get either a resolved value or an error back unless you loop infinitely and your logic never accepts a desired result and returns a value. – jfriend00 Sep 15 '16 at 03:20
  • I've worked on this for awhile, and improved my surrounding calls according to the patterns you suggested, but it seems that when I call promiseDelay, the pollAPI function returns, causing `main()` to continue with the `then`. So I guess I'm chaining the promises incorrectly, but I can't see where. I don't want to clutter the question with specific code, so here is a fiddle: https://jsfiddle.net/8sjcLjrt/ Once I know what was causing the problem, I'll comment here. Help would be appreciated though - I'm stumped! – fildred13 Sep 15 '16 at 15:47
  • The current state of that fiddle, is that the console prints, in order: `not finished yet, retrying` `2. job status found, so storing states and resolving` `1. getJobStatus finished, so returning and finishing promise` So even though I return the `promiseDelay` in `getJobStatus()`, for some reason the `then` in createLinkAPIJob acts as if the promise got resolved. – fildred13 Sep 15 '16 at 15:52
  • Since I had to use an anonymous function as the callback for the promiseDelay().then(), I had to return the call to pollAPI in order to keep the chain of promises going. Thanks so much for this answer, it helped me understand promises much better. – fildred13 Sep 15 '16 at 16:30