Executing then after catch

TGH picture TGH · Feb 11, 2015 · Viewed 10.5k times · Source

I have the following fiddle: http://jsfiddle.net/thelgevold/3uv9nnjm/6/

angular.module('hello',[]).controller('helloController',function($q){

    console.clear();
    function someService(){
       var deferred = $q.defer();
       deferred.reject({e:'error'}); 
       return deferred.promise;
    } 

    function callService(){
        return someService().then(function(obj){
           console.log('first then');
        }).
        catch(function(e){
            console.log('error1');
            var deferred = $q.defer();
            deferred.reject({e:'error'}); 
            return deferred.promise;
        });
    }

    callService().catch(function(e){
      console.log('error2');
    }).then(function(e){
      console.log('second then');
    });

});

It's essentially just a quick $q promise POC. My question is: Why does the last then clause get called when the promise is rejected? The output is as follows:

error1

error2

second then

I understand why error1/error2 are printed, but I thought the second then string should not be printed since the promise was rejected. I thought it would omit "second then" for the same reason the "first then" is omitted. Any thoughts?

Answer

JLRishe picture JLRishe · Feb 11, 2015

Before I get started, don't do this:

var deferred = $q.defer();
deferred.reject({e:'error'}); 
return deferred.promise;

Do this:

return $q.reject({e:'error'});

Or preferably, this:

return $q.reject(new Error('error'));

Beware the deferred antipattern.

Now, for the answer to your question.


The .catch() after your call to callService() is catching the error and not producing a new error. It has essentially "handled" the error, and the following .then() handler is free to be called.

The synchronous code equivalent of your example would be:

function someService() {
  throw { e: 'error' };
}

function callService() {
  try {
    var obj = someService();
    console.log('first then');
  } catch (e) {
    console.log('error1');
    throw { e: 'error' };
  }
}

var e;
try {
  e = callService();
} catch (e) {
  console.log('error2');
}

console.log('second then');

I think that if you look at it this way, it makes perfect sense.

The relevant text in the Promises/A+ spec is here. For all intents and purposes, you can view the catch handler as the same thing as an onRejected handler:

2.2.7. then must return a promise [3.3].

promise2 = promise1.then(onFulfilled, onRejected);

2.2.7.1. If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).

Basically, your onRejected handler is "returning" the value undefined, so the promise produced by catch() resolves with the value undefined.