1

I have a service that gets reports:

ReportsResource.getActiveUsers(). This uses $http and then returns a promise.

I then use it like this:

var request = ReportsResource.getActiveUsers();

request.then(populateActiveUsersTable, handleError);

But, the catch is that the request to get the active users report on the backend can take anywhere from a couple of seconds, to 30+ minutes.

  • If you make a request and no cached report is available, it generates the report, and then the request waits for data (again, could be 2 seconds or 30 minutes) for that request.

  • If you make a request and the report is currently being generated, it returns a response instantly telling you the report is not ready yet. At which point you can keep polling to see if the report is ready.

  • If the report is ready (cached), then it returns the response instantly with the report data.

What I need is wrap the request in a timeout that waits up to 10 seconds, and then aborts if the response takes longer than 10 seconds to complete, and starts polling the server to ask if the report is ready yet. But if the request resolves under 10 seconds, it should cancel the timeout and carry out the promise chain as normal.

Not really sure how to handle this one.

AgmLauncher
  • 7,070
  • 8
  • 41
  • 67

2 Answers2

0

Under the situation been given I think it is better to use WebSocket rather than timeout function. With WebSocket, you just need to register the function you need to run every time there's an update/change sent from the server. Instead of keeping polling, websocket require less resource and be more efficiency. But it needs a bit of work on back-end.

The implementation defers for different back-end language. You probably need to talk with the back-end people(or yourself). Hope this can give you some idea.

===Edit===

If you want to use service like timeout or interval, the code below should help:

//inside your controller
var pollingPromise = $q.defer(); //used to controll the fired $http request

var pollingActiveUsers = function() {

    ReportsResource.getActiveUsers(pollingPromise).then( function(data){
        //stop polling
        $interval.cancel(pollingProcess);
        //set to the controller's scope
        $scope.activeUsers = data;
        //populate the activeUsersTable
        populateActiveUsersTable();
    });
};

//init the first request
pollingActiveUsers();

//polling for every 10secs
var pollingProcess = $interval( function() {
    //resolve the previous polling request which mean cancel the previous $http request if it waits longer than 10 secs
    pollingPromise.resolve();

    //start another polling, a new pollingPromise is required
    pollingPromise = $q.defer();

    pollingActiveUsers();
}, 10000);

//In your service ReportsResource
//you have to make another change function getActiveUsers() to make this work, you have to pass the pollingPromise to the $http method, so you can cancel the $http request:
var function getActiveUsers = function(promiseObj) {
    return $http.get('someUrl', { timeout: promiseObj });
};

Few concerns will be taken:

  1. Already fired $http request should be canceled/resolved if it takes more than 10 secs. so the pollingPromise is the one we need. More info here: cancel unresolved promise
  2. a new $http request should be fired every 10 secs, $interval solve this issue and $interval.cancel() function will stop this interval.
  3. polling should stop immediately when receive the desired data

The code might need to be altered when you apply in your app.

Community
  • 1
  • 1
David Tao
  • 513
  • 5
  • 12
  • Yes, this would be ideal, unfortunately that's an architectural implementation that isn't doable in the scope of this particular piece of work. We have a roadmap to get there, but it requires some async processing changes on the BE, which has some knock-on effects etc. – AgmLauncher Apr 19 '16 at 14:00
0

Use angular's $q together with $timeout. Create a deferred with $q.defer(), create the request with a timeout, and forward the result in the then handler to resolve your deferred. If the request timeouts, start polling. Return the promise of the deferred immediately.

var d = $q.defer() // defered for the final result

function poll() {
    $http({...}).then( //poll request 
        function(res) {
            if (ready(res))
                d.resolve(res)
            else {
                $timeout(poll, 10000)
            }
        },
        function(err) {
            d.reject(err)
        })
}

$http({ timeout: 10000, ... }).then(
    function(res) {
        d.resolve(res)
    }, // return result directly
    function(err) { // error or timeout
        if (realError(err)) // check if real error 
            d.reject(err)
        else { //timeout
            $timeout(poll, 10000)
        }
    })
return d.promise

You can reuse the returned promise arbitrarily often invoking then to wait for or obtain the cached result.

bnord
  • 385
  • 1
  • 12