Search code examples
angularjspollingdeployd

AngularJS ongoing polling of server, with cancellation


I followed the excellent advice dispatched here (Server polling with AngularJS), but [think I] have a need to sometimes cancel the polling, to reinstate it later.

Specifically, I have a list of data. The client polls the server every 5 seconds with a timestamp of the "last synchronization" -- the last time that it conferred with the server. The server responds with any changes since that timestamp.

Sometimes the client may make a change itself, sending a PUT to the server.

I'm having an issue with the PUT request, I think, interfering with the poll (or vice-versa), causing data to get out of sync. I'd like to test this by canceling the poll until the PUT request has been approved.. but I just can't get to a place where the polling function can successfully call itself each time; issue a promise that's cancellable; and be restarted externally after it's canceled.

I got a little close with a service ("pulseService"), but I can't get all the way. It looks like this, but fails with "cannot read property 'poller' of undefined":

myModule.factory('pulseService', function($http, $rootScope, $timeout) {
  $rootScope.pulsePromise = null;
  var obj = {
    poller: function() {
      var thing = this;
      console.log("Here I am!");
      var semaphore = new Date().getTime();
      var query = {"timestamp": {'$gt': semaphore}};
      query = JSON.stringify(query);
      $http({method: 'GET', url: '/registrants', data: query}).
        success(function(data, status, headers, config) {
          $rootScope.error = false;
          $rootScope.$broadcast('pollFinished', data);
          $rootScope.pulsePromise = $timeout(thing.poller, 5000);
        }).
        error(function(data, status, headers, config) {
          $rootScope.error = true;
          semaphore = new Date().getTime();
          $rootScope.pulsePromise = $timeout(thing.startPolling, 15000);
        });
    }(),
    startPolling: function() {
      console.log(this);
      this.poller;
    }
  };
  return obj;
});

By request, here's a simplified version of my controller.. It might have a little kruft in it but I tried to simplify out stuff:

function regCtrl($scope, $http, $rootScope, $timeout, pulseService) {
  // ...
  // Doing stuff to initialize and gather data into $scope.attendees

  $scope.$on( 'pollFinished', function( event, data ) {
    var found = false;
    angular.forEach(data, function(resultVal, resultKey) {
      while (found === false) {
        angular.forEach($scope.attendees, function(attendeeVal, attendeeKey) {
          if (attendeeVal.id == resultVal.id) {
            $scope.attendees[attendeeKey] = resultVal;
            found = true;
          }
        });
      }
      found = false;
    });
  });

  // .. Logic for pushing a change to the server
  // .....
    $timeout.cancel($rootScope.pulsePromise);

    $http({method: 'PUT', url: '/registrants/'+attendee.id, data: query }).
      success(function(data, status, headers, config) {
        attendee.isHere = data.isHere;
        console.log("rerunning");
      }).
      error(function(data, status, headers, config) {
        $scope.error = true;
      });
  // ...

  var semaphore = new Date().getTime();
  // Kickoff the polling process
  pulseService.startPolling();
}
regCtrl.$inject = ['$scope','$http','$rootScope','$timeout','pulseService'];

Solution

  • I think the specific error you're getting is because, when you do $timeout(thing.startPolling, 15000), startPolling is unbound. So "this" inside startPolling is undefined.

    I think you could just replace both $timeout calls with $timeout(obj.poller, ...) and get rid of startPolling.

    Or you can just bind the method like $timeout(thing.poller.bind(thing), 5000).