dewd
dewd

Reputation: 4429

Angularjs $http interceptors - Cannot generate call to requestError

I'm using $http interceptors to capture all events following an ajax submission. For some reason, I am not able to throw a requestError. I've set up a test app to try and call requestError, but so far I can only get multiple responseErrors.

From angularjs docs:

requestError: interceptor gets called when a previous interceptor threw an error or resolved with a rejection.

This is my test code.

        .factory('httpInterceptor',['$q',function(q){

            var interceptor = {};

            var uniqueId = function uniqueId() {
                return new Date().getTime().toString(16) + '.' + (Math.round(Math.random() * 100000)).toString(16);
            };

            interceptor.request = function(config){
                config.id = uniqueId();
                console.log('request ',config.id,config);
                return config;
            };

            interceptor.response = function(response){
                console.log('response',response);
                return response;                    
            };

            interceptor.requestError = function(config){
                console.log('requestError ',config.id,config);
                return q.reject(config);
            };

            interceptor.responseError = function(response){
                console.log('responseError ',response.config.id,response);
                return q.reject(response);                    
            };

            return interceptor;

        }])

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

        .controller('MainCtrl',['$http',function($http){

            var mainCtrl = this;

            mainCtrl.method = null;
            mainCtrl.url = null;

            var testHttp = function testHttp() {

                $http({method:mainCtrl.method,url:mainCtrl.url}).then(
                        (response)=>{console.log('ok',response);},
                        (response)=>{console.log('reject',response);}
                );
            };

            //api
            mainCtrl.testHttp = testHttp;

        }])

I've tried multiple ways of creating http errors, and every time only responseError gets called. Things I've tried:

SIMILAR QUESTIONS

1) This question seems to have the answer: When do functions request, requestError, response, responseError get invoked when intercepting HTTP request and response?

The key paragrapgh being:

A key point is that any of the above methods can return either an "normal" object/primitive or a promise that will resolve with an appropriate value. In the latter case, the next interceptor in the queue will wait until the returned promise is resolved or rejected.

but I think I'm doing what it stipulates, viz random sleep by the server but no luck. I am getting reponseErrors out of order from the request ie as soon as the server responds.

2) A similar question was asked about 1 year ago: Angular and Jasmine: How to test requestError / rejection in HTTP interceptor?

Unfortunately, it only provides an explanation for interceptors. It does not answer my question.

I have tested in Chrome and Firefox. I hope you understand, I've done my best to find a solution to this, but I haven't come across a solution as yet.

Upvotes: 4

Views: 3903

Answers (3)

Derek718
Derek718

Reputation: 373

I know I'm years late, but I just came across the same problem and I didn't find any of the other answers particularly helpful. So, after spending a number of hours studying AngularJS interceptors, I'd like to share what I learned.

TL;DR

Interceptors are not intuitive, and there are a lot of "gotchas". The thread author and I got caught in a few of them. Problems like this can be fixed by better understanding the execution flow that happens behind the scenes. Most specific to this thread are Gotchas #3 and #6 near the end of this post.

Background

As you know, the $httpProvider has a property called "interceptors", which starts as an empty array and is where one or more interceptors can be stored. An interceptor is an object that has four optional methods: request, requestError, response, and responseError. The documentation says little about these methods, and what it does say is misleading and incomplete. It is not clear when these are called and in what order.

Explanation By Example

As mentioned in other comments/answers, the interceptor methods all end up linked together in a big promise chain. If you aren't familiar with promises, interceptors won't make any sense (and neither will the $http service). Even if you understand promises, interceptors are still a little weird.

Rather than trying to explain the execution flow, I'll show you an example. Let's say that I've added the following three interceptors to my $httpProvider.interceptors array.

Diagram of 3 interceptors

When I make a request via $http, the execution flow that happens behind the scenes looks like the following. Note that green arrows indicate that the function resolved, and the red arrows indicate that the function rejected (which will happen automatically if an error is thrown). The labels next to the arrows indicate what value was resolved or rejected.

enter image description here

Wow, that's super complicated! I won't go through it step by step, but I want to point out a few things that might leave a programmer scratching their head.

Notable Bug-Causing Weirdness ("Gotchas")

  1. The first thing to note is that, contrary to popular belief, passing a bad config object to $http() will not trigger a requestError function -- it won't trigger any of the interceptor methods. It will result in a normal old error and execution will stop.

  2. There is no sideways movement in the flow -- every resolve or reject moves execution down the chain. If an error occurs in one of the success handlers (blue), the error handler (orange) in the same interceptor is not called; the one on the next level down is called, if it exists. This leads to gotcha number 3...

  3. If you defined a requestError method in the first interceptor, it will never be called. Unless I'm reading the angularjs library code incorrectly, it seems that such a function is completely unreachable in the execution flow. This was what caused me so much confusion, and it seems it may have been part of the problem in the original question as well.

  4. If the request or requestError methods of the last interceptor reject, the request will not be sent. Only if they resolve will angular actually attempt to send the request.

  5. If the request fails to send OR the response status is not 2XX, it rejects and triggers the first responseError. That means your first responseError method has to be able to handle two different kinds of inputs: If the "send" function failed, the input would be an error; but if the response was a 401, the input would be a response object.

  6. There is no way to break out of the chain once it starts. This also seemed to be part of the problem in the original question. When the last requestError rejects, it skips sending the request, but then the first responseError is immediately called. Execution doesn't stop until the chaining is complete, even if something fails early on.

Conclusion

I assume the author of this thread resolved (no pun intended) their problem long ago, but I hope this helps someone else down the line.

Upvotes: 2

Estus Flask
Estus Flask

Reputation: 222309

This happens because the request isn't rejected at any point. It is supposed to be used like that:

app.factory('interceptor1', ['$q', function ($q) {
  return {
    request: function (config) {
      console.log('request', config);
      if (config.url === 'restricted')
        return $q.reject({ error: 'restricted', config: config });
    }
  };
}]);

app.factory('interceptor2', ['$q', function ($q) {
  return {
    requestError: function (rejection) {
      console.log('requestError', rejection);      
      if (rejection.error === 'restricted')
        return angular.extend(rejection.config, { url: 'allowed' });

      return $q.reject(rejection);
    }
  };
}]);

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

Notice that interceptors are supposed to work in stack (starting from transform* hooks in $http request), so the request can't be rejected and recovered within a single interceptor.

Upvotes: 5

ruby_newbie
ruby_newbie

Reputation: 3275

You are raising the responseError because all of your examples have errors in their responses. You can get a request error by trying to send invalid json in your request or improperly formatting your request.

Upvotes: 0

Related Questions