crashbus crashbus - 4 months ago 20
AngularJS Question

Angular $q catch block resolves promis?

The past view days I read a lot of best practices in handling with promises. One central point of the most postings where something like this:


So if you are writing that word [deferred] in your code
[...], you are doing something wrong.1


During experimenting with the error handling I saw an for me unexpected behavior. When I chain the promises and It run into the first catch block the second promise gets resolved and not rejected.

Questions




  • Is this a normal behavior in other libs / standards (e.g. q, es6), too and a caught error counts as solved like in try / catch?

  • How to reject the promise in the catch block so that the second gets, called with the same error / response object?



Example



In this example you see 'I am here but It was an error'

Full Plunker

function BaseService($http, $q) {
this.$http = $http;
this.$q = $q;
}

BaseService.prototype.doRequest = function doRequest() {
return this.$http({
method: 'GET',
url: 'not/exisint/url'
})
.then(function (response) {
// do some basic stuff
})
.catch(function(response) {
// do some baisc stuff e.g. hide spinner
});
}


function ChildService($http, $q) {
this.$http = $http;
this.$q = $q;
}

ChildService.prototype = Object.create(BaseService.prototype);

ChildService.prototype.specialRequest = function specialRequest() {
return this.doRequest()
.then(function (response) {
alert('I am here but It was an error');
})
.catch(function (response) {
// do some more specific stuff here and
// provide e.g. error message
alert('I am here but It was an error');
return response;
});
}


Workaround:



With this workaround you can solve this problem, but you have to create a new defer.

BaseService.prototype.doRequest = function doRequest() {
var dfd = this.$q.defer();

return this.$http({
method: 'GET',
url: 'not/exisint/url'
})
.then(function (response) {
// do some basic stuff
dfd.resolve(response);
})
.catch(function(response) {
// do some basic stuff e.g. hide spinner
dfd.reject(error);
});
}

Answer

Your workaround is almost correct, you can simplify it to the following:

BaseService.prototype.doRequest = function doRequest() {  
  return this.$http({
      method: 'GET',
      url: 'not/exisint/url'
    })
    .then(function (response) {
      // do some basic stuff
      return response;
    }, function (error) {
      return this.$q.reject(error);
    });
}

$q.reject is a shortcut to create a deferred that immediately get's rejected.

  1. Yes, this is default behaviour in other libraries as well. .then or .catch simply wraps the return value into a new promise. You can return a rejected promise to make the .catch chain work.

You can also do the opposite, for instance when you want to reject the promise in the success callback for whatever reason:

function getData() {
   return this.$http.get(endpoint).then(result => {
      // when result is invalid for whatever reason
      if (result === invalid) {
         return this.$q.reject(result);
      }

      return result;
   }, err => this.$q.reject(err));
}

getData().then(result => {
   // skipped
}, error => {
   // called 
});
  1. See example above
Comments