0

I have been using angular's $http service for a project. Before finishing the project, I noticed that the .success and .error callbacks have been deprecated. So now we have .then and .catch clauses, fine.

I have a service, that wraps the calls to certain API. Inside that service I wanted to "install" a default error handler in case of a unauthorized request. I used to do it this way:

post = function(encodedData ,headers ,url ) {
    headers.Accept = '*/*';

        this.setAutentication();

    return $http({
      'url': url,
      'method': 'POST',
      'data': encodedData,
      'headers': headers
    }).error( this.callUnauthorizedHandler.bind(this) );
  };

As you can see, I return the $http promise with an error handler already attached. Since the .error callback returns the original promise, everything worked like a charm. Now I have to first save the promise to a variable, attach the error handler using catch and then return the promise. Not a big problem, but I feel like there is no way to attach several "error" handlers to a single promise, so if I add another catch clause, I think I will be overwriting the already installed one. Is this correct? What is the correct way of managing this? All what I have read about promises does not specifies if this idea is valid or absolutely stupid.

EDIT:

I have found this answer: https://stackoverflow.com/a/22415684/1734815 Seems that i have to throw an error from my "default" error handler if it is now able to handle the error by itself, but I'm not sure about this.

Community
  • 1
  • 1
Danielo515
  • 5,996
  • 4
  • 32
  • 66
  • You could just do `return $http({...}).catch(this.callUnauthorizedHandler...)` – PSL Nov 02 '15 at 21:36
  • That's the first thing I tried, but it is not returning the original promise. It returns whatever is the result of the catch sentence. – Danielo515 Nov 02 '15 at 21:38
  • Yes that is what is expected. I misunderstood your question perhaps. So you want to handle the error but still return a success to the consumer? Also btw you can centralize this in an interceptor if you are repeating the same stuff everywhere. – PSL Nov 02 '15 at 21:38
  • "I feel like there is no way to attach several "error" handlers to a single promise". What is your use case? All of the error handling should be able to be encapsulated in one single `catch` callback. – Josh Beam Nov 02 '15 at 21:39
  • Maybe my fault. What I want is to, transparently attach an error handler in case I get a "404" unauthorized response. Other potentials functions that are going to use the returned promise do not need to be aware of that error handler. – Danielo515 Nov 02 '15 at 21:40
  • Hello @JoshBeam, as I said on my previous comment, I want to handle one particular error response transparently , and leave all the rest responses (both errors and non errors) to the functions and methods that are going to "receive" the promise. – Danielo515 Nov 02 '15 at 21:42
  • @Danielo515 in your error handler, if you just return without (`return $q.reject`) subsequent chains will get their `then` block invoked. if you `return $q.reject(stuff)` it will just not run the `then` block in the subsequent chain. – PSL Nov 02 '15 at 21:43
  • No, multiple `catch` invocations on one promise don't overwrite each other. But notice that they [chain and handle exceptions](http://stackoverflow.com/q/16371129/1048572) (unlike `.error`), so if you want to attach multiple ones to the same promise you will have to store the promise separately. – Bergi Nov 02 '15 at 21:51

1 Answers1

2

The way promises work is that errors propagation stop after the first catch.

somePromise().then( /* ... */ ).catch( function () {
    console.log( 'Error 1' );
} ).catch( function () {
    // Will not execute
    console.log( 'Error 2' );
} );



// On the other hand, in this case...
var p = somePromise().then( /* ... */ );

p.catch( function () {
    console.log( 'Error 1' );
} );

p.catch( function () {
    // Will execute
    console.log( 'Error 2' );
} );

This happens because the promise a catch returns is actually a different object.

var p = somePromise.then( /*...*/ );

var x = p.catch( /*...*/ );

p === x // False

What this means is that everytime you call a .then (.catch is shorthand for .then( null, fn )) you are returning a new promise.

So let's say you wanted to intercept an error and still let it propagate. There are two ways.

Example 1

var p = somePromise();

p.catch( /*...*/ );

return p;

This works because we are attaching multiple listeners to the same promise. So even if someone called another .catch, it would still catch it. The drawback: After someone called one .then (thus creating anew promise instance in the chain), any .catch below wouldn't catch the error (because it was being caught upwards in the chain).

Example 2 Preferred

return somePromise().then( /*...*/ ).catch( function ( reason ) {
   this.callUnauthorizedHandler( reason );

   // Here we can leverage the fact that all promises 
   // return a new promise, so we can just forge a promise 
   // that will reject right away with the value we want
   return $q.reject( reason );
} );
Pedro M. Silva
  • 1,298
  • 2
  • 12
  • 23
  • Thank you very much for your very detailed explanation. Note that I only want to handle one specific type of error by default and leave the full management of the promise to the methods/functions receiving it, so I will not call any `.then` from the method returning the promise. I think the best approach in my situation is some middle solution between Example1 and two. Install a `catch` evaluate the error, and if it can not be handled then return `$q.reject(reason)`. Should I return the promise chained with the catch ? Here is how I will do it: http://plnkr.co/edit/qEmiX8sd5mFdVX6zCqi0 – Danielo515 Nov 03 '15 at 08:52
  • Yes, you should return the promise created by the catch, like in the plnkr. – Pedro M. Silva Nov 03 '15 at 09:00
  • Thank you very much Pedro. I'll do it that way – Danielo515 Nov 03 '15 at 10:17