1

In every $http call with .then(successFunc) and .catch(errFunc) (alternatively then(successFunc, errFunc) the then/success is always called (the promise is successfully resolved).

Example

$http.get('/some/resource') 
//   /some/resource --> HTTP 400
    .then(function(response)
    {
        console.log('success');
    })
    .catch(function(reason)
    {
        console.log('error');
    })

// Console: 
// - success

Is this the intended behaviour or what causes this?

georgeawg
  • 48,608
  • 13
  • 72
  • 95
Aides
  • 3,643
  • 5
  • 23
  • 39
  • This is not the default behaviour, but might be due to a response interceptor you have used in your application. Usually the response interceptors are global and you might not be aware of it's presence if it is a large application. Could you isolate this code in a fiddle to reproduce the same issue? – arunkjn Sep 13 '16 at 11:24
  • I created this as self-answered Q&A question to keep it around for other people since there was no similar question here on SO. You describe exactly the issue I wanted to point out with this post (also see my in-depth answer). Still thanks for the answer :) – Aides Sep 15 '16 at 12:30

2 Answers2

1

No, this is not intended behaviour. Normally it should call .then() on HTTP 2xx and .catch() on HTTP 4xx and HTTP 5xx (not sure about the others).

The described behaviour is probably caused by another .catch() returning a resolved promise.

In a slightly changed example:

//In some Service:
function getResources()
{
    $http.get('/some/resource') 
    //   /some/resource --> HTTP 400
        .then(function(response)
        {
            console.log('service success');
            return response;
        })
        .catch(function(reason)
        {
            console.log('service error');
            // IMPORTANT: although this is returned 
            // in a .catch() it returns a resolved promise
            return reason;
        });
}

//In some Controller:
someService.getResources()
    .then(function(response)
    {
        console.log('controller success');
    })
    .catch(function(reason)
    {
        console.log('controller error');
    });


// Console:
// - service error
// - controller success

Note that this can also be caused by a registered http interceptor:

$httpProvider.interceptors.push(function($q)
{
    return {
        'response': function(response)
        {
            // do something
            return response;
        },
        'responseError': function(rejection)
        {
            // do something
            return rejection; // <-- this causes the problem
            // instead do
            return $q.reject(rejection);
        }
    }
}
Aides
  • 3,643
  • 5
  • 23
  • 39
1

Your example converts a rejection to a resolved promise.

$http.get('/some/resource') 
//   /some/resource --> HTTP 400
    .then(function(response)
    {
        console.log('success');
        //IMPORTANT -- return to chain data
        return response;
    })
    .catch(function(reason)
    {
        console.log('error');
        //IMPORTANT -- throw to chain rejections
        throw reason;
    })

When a catch handler omits a throw statement, the function returns undefined which converts the rejection to a promise that resolves undefined.

Look in your code for a http interceptor that is following the same erroneous pattern.

The rule in functional programming is always return (or throw) something.

georgeawg
  • 48,608
  • 13
  • 72
  • 95
  • This is the first time I've seen the `throw` keyword in use with promises. Do you have any example demonstrating the correct behaviour? – Aides Sep 15 '16 at 12:32
  • See [Angular execution order with `$q`](http://stackoverflow.com/a/34326388/5535245) – georgeawg Sep 15 '16 at 12:38
  • I just tested it in a fiddle - works as described, however it still propagates the error to the browser, hence I would rather prefer `return $q.reject(reason)` over `throw reason` (see [Angular $http docs](https://docs.angularjs.org/api/ng/service/$http#interceptors)). – Aides Sep 15 '16 at 15:14