2

As per my other recent questions, I'm trying to persist data to a server for an angular application that's targeted at mobile devices (unstable connections), so it should keep trying the request until success.

How can I do this with promises?

At the moment I've got:

Service:

 this.addObject = function addObject(object) {
        var deferred = $q.defer();

      var httpConfig = {
        method: 'POST',
        url: 'http://api.foo.com/bar',
        data: object
      }

      setTimeout(function() {
          $http(httpConfig).
              success(function(data, status) {
                  deferred.resolve('Woohoo!');
              }).
              error(function(data, status) {
                  deferred.reject('Couldnt reach server this time...');
              });
      }, 3000);

        return deferred.promise;
    }

Controller:

myService.addObject(myObject)
            .then(function(message) {
                console.log(message);
            }, function(message) {
                console.log(message);
            });

I can't remove the reject callback, as the code won't seem to execute without it, but once the reject is called, it breaks the setTimeout loop. How can I force the promise to repeat until the success callback?

Anonymous
  • 6,181
  • 7
  • 45
  • 72

5 Answers5

3

This is the proper form of the answer at AngularJS service retry when promise is rejected

 this.addObject = function addObject(object) {
    var counter = 0;
      var deferred = $q.defer();

      var httpConfig = {
        method: 'POST',
        url: 'http://api.foo.com/bar',
        data: object
      }

      var doRequest = function() {
        counter++;
        var self = this,args = arguments;
      $http(httpConfig).
          success(function(data, status) {
              deferred.resolve('Woohoo!');
          }).
          error(function(data, status) {
            //just fail already, it's not working
            if(counter > 5) {
                return deferred.reject('Couldnt reach server this time...');
            }

            //this will re-call doRequest();
            args.callee.apply(self);
          });

      }

      doRequest();

        return deferred.promise;
    }
Community
  • 1
  • 1
Adam Jenkins
  • 51,445
  • 11
  • 72
  • 100
0

As you're doing with with $http, http interceptors can do this. If you want a http request to infinitely loop until it returns a success:

app.config(['$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push('RetryInterceptor');
  }]);

app.factory('RetryInterceptor', function($timeout, $injector) {
  return {
   'responseError': function(rejection) {
      // Manual inject to work around circular dependency issue
      var $http = $injector.get('$http');
      return $timeout(function() {
        return $http(rejection.config);
      },3000);
     }
  }
});

This works due to how the initial call to $http won't resolve until (in this case) all the responseError interceptors have been resolved.

You can see this in action at http://plnkr.co/edit/QAa9oIK4lTM6YwccEed3?p=preview (looking in the console log there is a failed request every 3 seconds.)

Note: there might need to be more logic to make sure it only retries on the right sort of error. i.e. it's not a real 404 where in fact the browser is to blame, for example.

Michal Charemza
  • 25,940
  • 14
  • 98
  • 165
0

it should keep trying the request until success. How can I do this with promises?

By "recursively" calling the function again in the error handler so that you're resolving the promise with the result of the next try.

this.addObject = function addObject(object) {
  var httpConfig = {
    method: 'POST',
    url: 'http://api.foo.com/bar',
    data: object
  }

  return $http(httpConfig).then(function(data, status) {
    return 'Woohoo!';
  }, function(data, status) {
    throw 'Couldnt reach server this time...';
  });
}

this.addObjectForever = function addObject(object) {
  var that = this;
  return this.addObject(object).then(null, function(err) {
    console.log("Failed this time");
    return $timeout(3000).then(function() {
      console.log("Trying again now");
      return that.addObjectForever(object);
    });
  });
};

instance.addObjectForever(obj).done(function() {
  console.log("It eventually worked");
});
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

quite crowded here :) my solution:

angular.module('app', [])
             .service('Service',function($q,$http) {

                this.addObject = function(object) {
                     var httpConfig = {
                        method: 'POST',
                        url: 'http://api.foo.com/bar',
                        data: object
                    }
                    var deferred = $q.defer();
                    $http(httpConfig)
                            .success(function(data, status) {
                                deferred.resolve('Woohoo!');
                            })
                            .error(function(data, status) {
                                deferred.reject('Couldnt reach server this time...');
                            });

                   return deferred.promise;

                };

             })
             .controller('MainCtrl',function($scope,$interval,Service){
                 /*Service.addObject({})
                    .then(function(message) {
                        console.log(message);
                    }, function(message) {
                        console.log(message);
                    });*/
                $interval(function(){
                    Service.addObject().then(function(message) {
                        console.log(message);
                    }, function(message) {
                        console.log(message);
                    });
                },1000);
             })
Whisher
  • 31,320
  • 32
  • 120
  • 201
0

Uhm. Even though there are HTTP interceptors for this particular case, one of the advantages of working with promises instead of callback is that you can use higher order functions.

// both p and q are functions that create a promise
p = makeSomePromise(options)
q = repeat(10, p)

That is, for instance, a function that takes a promise making function and produces a new promise making function that retries the other promise repeatedly, until a max.

For example: (This is for nodeJS using kew, but you should get the point)

var Q = require('kew');

function retryPromise(n, mkPromise) {
    if(n > 0)
        return mkPromise()
            .fail(function(){
                console.log('failed promise, retrying maximum ' + n + ' more times');
                return retryPromise(n - 1, mkPromise);
            });

    return mkPromise();
}


(function(){
    var mkOp = function(){
        if(Math.random() > .1)
            return Q
                .fcall(function(){return 1;})
                .then(function(){throw Error('aah');});

        return Q.fcall(function(){return 'yay'});
    };

    retryPromise(10, mkOp)
        .then(function(data){
            console.log(data);
        })
        .fail(function(err){
            console.log('failed', err);
        });
}());
wires
  • 4,718
  • 2
  • 35
  • 31