Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retry request with $http interceptor

I'm sure there is an easy way to do what I want, I just cant wrap my head around it. How can I get the http interceptor in angular to retry a request if it fails? I imagine I would have to build some sort of promise in the request right? Then in the response I would have to check whether the response was an error and if so, do the promise? How is that done? I have been trying to adapt the example here: http://docs.angularjs.org/api/ng.$http

The reason I am trying to use an interceptor is because I need to add the token and a few other things to the request url, as well as some things to handle xdr's.

like image 459
jensengar Avatar asked Nov 19 '13 15:11

jensengar


3 Answers

Here's a $http interceptor that (immediately) replays timed out request(s) (i.e. response status 0). There were two non-obvious (to me!) elements to constructing this example:

  1. How to call $http from within the interceptor - simply adding $http to the dependency list didn't work as angular complains of a circular dependency
  2. How to reference the original request from the response object in order to retry it

This answer addresses both topics but is more involved so I include a simplified version below.

joinApp.factory('httpResponseErrorInterceptor',function($q, $injector) {
  return {
    'responseError': function(response) {
      if (response.status === 0) {
        // should retry
        var $http = $injector.get('$http');
        return $http(response.config);
      }
      // give up
      return $q.reject(response);
    }
  };
});

joinApp.config(function($httpProvider) {
  $httpProvider.interceptors.push('httpResponseErrorInterceptor');
});

In your actual implementation, you would likely want more sophisticated http response code processing, a limit to the number of times you retry, etc.

like image 187
mygzi Avatar answered Nov 11 '22 19:11

mygzi


For the sake of completeness, here is a version of mygzi's answer with retry delay:

.factory('httpResponseErrorInterceptor', ['$injector', '$q', '$timeout', function($injector, $q, $timeout) {
  return {
    'responseError': function(response) {
      if (response.status === 0) {
        return $timeout(function() {
          var $http = $injector.get('$http');
          return $http(response.config);
        }, 15000);
      }
      return $q.reject(response);
    }
  };
}])

.config(function($httpProvider) {
  $httpProvider.interceptors.push('httpResponseErrorInterceptor');
});

$timeout returns a promise that is completed with what is returned from the function parameter, so we can conveniently just return the $http call wrapped in $timeout.

like image 33
user1338062 Avatar answered Nov 11 '22 17:11

user1338062


I've done this for an app I wrote before. To do the retry wrap the $http usage in a function (or preferably in a service so that you can reuse it easily). If the request fails, call the function again.

The trick is then to pass the promise object along with each request. If you create a new promise with each request then it won't match the one you returned to the original caller, so the original caller won't get his promise resolve once the request passes.

So it is something like this (note that the defer object is passed along in each retry):

app.service('HttpService', ['$q', function($q) {
  this.makeRequest = _makeRequest;

  function _makeRequest(url, data, deffered) {
    // We want to keep the same promise for each request, so we don't loose track
    if (deferred === undefined) {
      deferred = $q.defer();
    }

    // Now make the request
    $http({...}).success(...).error(
      function(){
        // If some condition
        _makeRequest(url, data, deffered);
      }
    )

    // Lastly return the promise
    return deferred.promise;
  }
}])
like image 2
Erik Honn Avatar answered Nov 11 '22 19:11

Erik Honn