4

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:

  • Get server to return different types of error for every request, e.g. 400 and 500.
  • Get the server to sleep random times, to get some later requests to respond with an error before earlier requests. Same resource, same server response.
  • Generate 404 errors by requesting resources which don't exist.
  • Disconnecting from the internet (responseError -1).

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.

2
  • 1
    All your examples involve the response from the server - or the lack of it, hence it is responseError. As the name suggests, requestError should handle the errors on client side, i.e. when config object isn't correct or whatever. Commented Sep 7, 2016 at 14:59
  • @estus thx for the pointer. Throwing an error and returning a $q.reject() in the request didn't work. I'll try other things as suggested by rubie_newbie below. Commented Sep 7, 2016 at 15:53

3 Answers 3

5

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.

Sign up to request clarification or add additional context in comments.

6 Comments

I think your point about separating the interceptors is the most important one. I managed to get requestError called. If I $q.reject(config) within requestError, responseError is subsequently called - followed by any then error handlers. Is this always to be expected, or is there a way to finish the transaction at requestError?
End request* interceptor chain with rejection, it won't do a request then. $http forms a promise chain from request interceptors and ends it with a server request. It can't catch it within the same interceptor, because it makes a .then(..., ...) chain from request interceptors, not .then(...).catch(...). You can't make a 'final' rejection because each following interceptor should have a chance to catch this rejection, if this was your question.
"End request interceptor chain with rejection, it won't do a request then" I did end the request in the requestError handler with a $q.reject(config). The responseError handler was still subsequently called though. Apologies, but I don't understand the rest of your answer.
In order for the request to not be executed, responseError from the last interceptor should return a rejection. If there are several interceptors with responseError, there's no way to execute responseError hook from one interceptor and skip responseError hooks from the rest.
Yes, this is how promises work. As said above, request hooks from all interceptors are translated into single promise chain, and you cannot quit in the middle of the chain.
|
2

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.

1 Comment

Yes, I "resolved" a good while ago, but hopefully some people will find your answer useful. I'll certainly check it as reference if I ever come across interceptor problems again. I'm still using AngularJS! Haven't taken the leap yet!
0

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.

2 Comments

Thx for the pointer @ruby_newbie. I did try throwing an error in the request handler, but still got a responseError. I'll try something like malformed json as you suggest and get back to you with the results (hopefully within the next 24 hours).
FYI, I had to separate the interceptors. As estus's answer shows below. Appreciate your input in any case.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.