4

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);
    });
}
Community
  • 1
  • 1
crashbus
  • 1,678
  • 18
  • 37

2 Answers2

2

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
Dieterg
  • 16,118
  • 3
  • 30
  • 49
  • Would you prefer 'this.$q.reject(result)' over 'throw' like georgeawg described in his answer? – crashbus Mar 07 '16 at 09:42
  • 1
    Both give the same result. But if you use a native JavaScript promise and want to reject async it will not be possible with throw while it is possible with reject. i.e. `new Promise((resolve, reject) => setTimeout(() => throw 'err', 10)` will not work while `new Promise((resolve, reject) => setTimeout(() => reject('err'), 10)` will work fine – Dieterg Mar 07 '16 at 10:17
  • @Dieterg so if I'm in a then or catch and I want to resolve and reject the `var example = $http.post().then(function() { return true; }).catch(function() { return $q.reject(false); });` would result in example eventually equally either true or false, right? – mtpultz Sep 25 '16 at 03:12
1

Just to add to Dieterg's answer and to your workaround, you can also wrap the code into $q constructor:

BaseService.prototype.doRequest = function doRequest() {
   return $q(function (resolve, reject) {
      $http.get('not/exisint/url').then(function (response) { // success
          /* do stuff */
          resolve(response);
      }, function (error) { // failure
          /* do stuff */
          reject(error);
      });
   });
};
Community
  • 1
  • 1
Ricardo Nolde
  • 33,390
  • 4
  • 36
  • 40