14

I have a "cancellable" angularJs $http call like this:

var defer = $q.defer()
$http.post("http://example.com/someUrl", {some: "data"}, {timeout: defer.promise})

And I cancel that ajax request using defer.resolve() because some logic requires it.


Some where else in my code, I have an iterceptor like this:

angular.module("services.interceptor", arguments).config(function($httpProvider) {
     $httpProvider.interceptors.push(function($q) {
        return {
          responseError: function(rejection) {
            if(rejection.status == 0) {
              alert("check your connection");
            }
           return $q.reject(rejection);
        }
      };
    });
  });
});

Problem:

If there is an Internet connection problem, ajax fails with status 0 and interceptor catches it. If ajax is cancelled by the timeout promise, than status is also 0 and interceptor catches it.

I can't find out if it is cancelled or got error in responseError handler.

My naive approach is to check if timeout is defined like this:

responseError: function(rejection) {
  if(rejection.status == 0 && !rejection.config.timeout) {
    alert("check your connection");
  }
  return $q.reject(rejection);
}

It only guarantees that, there is a timeout condition on that request, not it failed because of it. But it is better than nothing.

Are there a really working way of determining if ajax is failed or cancelled?

I'm using AngularJs 1.1.5

3
  • Can you get the HTTP status code? Commented Aug 15, 2013 at 10:49
  • @DavinTryon In both cases, cancelling and network error status code returns as 0. Commented Aug 15, 2013 at 12:13
  • Getting exactly this issue at the moment (with angular 1.2.0) Commented Nov 13, 2013 at 3:14

5 Answers 5

5

I ended up doing this using a flag, which is checked within the error handler.

I started off following the advice at the end of this AngularJS Github issue: https://github.com/angular/angular.js/issues/1159#issuecomment-25735438

This led me to build a service which used a promise to manually "timeout" the http call - as you have done below - by copying the linked plunk from that issue: http://plnkr.co/edit/P8wKns51GVqw5mwS5l5R?p=preview

But this wasn't quite complete, as this didn't address the issue you raised; in the error handling, how to determine if the call was a genuine server outage / timeout, or simply a manually triggered timeout. In the end, I had to resort to storing it in another variable (similar to the timeout promise), which can be seen in this plunk: http://plnkr.co/edit/BW6Zwu

The main method of this code looks as follows

return function (url) {
    var cancelQuery = null;
    var queryCancelled = false;

    return function runQuery(query) {
      if (cancelQuery) {
        queryCancelled = true;
        cancelQuery.resolve();
      }
      cancelQuery = $q.defer();
      return $http.
        get(url, { params: { query: query }, timeout: cancelQuery.promise }).
        then(function (response) {
          cancelQuery = null;
          return response.data;
        }, function (error) {
          if(queryCancelled) {
            console.log("Request cancelled");
            queryCancelled = false;
          } else {
            console.log("Actual Error !");
          }
        });
    };
  };

Not too elegant, but it seems to work and avoids any nasty race conditions from what I have observed.

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

2 Comments

Thanks for the approach. I've wrapped my $http and the given cancelPromise if any with my promises and checked their status as you did in your example by a boolean flag. Than I altered the original response.status = 0 to 1 for cancelled promises to understand the difference. Thanks!
I made this slightly more elegant (at least to my own eye) by setting queryCancelled as a property of cancelQuery. Might as well take advantage of javascript's nature.
3

If you abort request using timeout, the timeout is listed in config of response.

Check for .config.timeout in your response object:

function config($httpProvider) {

    $httpProvider.interceptors.push(function ($q, $injector) {
        return {
            request: function (config) {
                return config;
            },

            response: function (response) {
                return response || $q.when(response);
            },

            responseError: function (response) {
                switch (response.status) {
                    case -1:
                    case 500 :
                    case 501 :
                    case 502 :
                    case 503 :
                    case 504 :
                        // the webapp sometimes aborts network requests. Those that dropped should not be restarted
                        if (response.status === -1 && response.config.timeout) {
                            break;
                        }
                        return retryRequest(response.config, response.status);
                }
                // otherwise
                return $q.reject(response);
            }
        };
    });
}

1 Comment

In my case whether my request is cancelled by a timeout or I get a 401 from the server, response.config.timeout is set and response.status is -1.
0

I know the question is already a little bit older, but I came upon it looking for a solution for the same problem. Other than in the answer already given I don't want to check a flag which I set at the same time as i cancle the AJAX request. It could already be a new request, which wasn't cancled.

So I found a different solution for me. Perhaps someone can make use of it, althoug it isn't an up to date question.

The quintessence of my solution is, to check not only the status. If the data is also null, it was a cancled request.

var defer = $q.defer()
$http.get("http://example.com/someUrl", {timeout: defer.promise})
  .error(function(data, status){
      if (data === null && status === 0) {
          // the request was cancled
      }
   });

Comments

0

You can use (rejection.config.timeout.status != 499):

...timeout.promise.status = 499;
...timeout.resolve();

responseError: function(rejection) {
  if(rejection.status == 0 && rejection.config.timeout.status != 499) {
    alert("check your connection");
  }
  return $q.reject(rejection);
}

instead of:

responseError: function(rejection) {
  if(rejection.status == 0 && !rejection.config.timeout) {
    alert("check your connection");
  }
  return $q.reject(rejection);
}

By the way in this case you don't need additional request decorator.

Comments

0

I was able to determine if the request was cancelled in Chrome v55 by checking the global event object's property type against the string "abort" (code below). I had the problem that ongoing requests were cancelled when navigating to another page and triggered the responseError.

angular.module("services.interceptor", arguments)
  .config(function($httpProvider) {
     $httpProvider.interceptors.push(function($q, $window) {
        return {
          responseError: function(rejection) {
            if($window.event.type === "abort") {
               // handle cancelled here
            } else if(rejection.status < 1) {
              alert("check your connection");
            }
           return $q.reject(rejection);
        }
      };
    });
  });
});

(also: The status property seems to have changed from 0 to -1 for network errors, so I wrote rejection.status < 1))

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.