re-rejecting promises


Let’s pretend that you have a bit of code like below…

    
angular.module('demo', [])

  .controller('LoginController', function(loginService) {
    var vm = this;
  
    vm.login = function login() {
      loginService.login(vm.username, vm.password)
        .then(function () {
          setBackground('green');
        })
        .catch(function () {
          setBackground('red');
        });
    }
  
    function setBackground(color) {
      angular.element(document.body).css({'background-color': color});
    }
  })

  .factory('loginService', function ($http, $q) {
    return {
      login: function login(username, password) {
        return $http.post('whatevs', {u: username, p: password})
          .then(function (response) {
            return response.data.token;
          })
          .catch(function (reason) {
            console.log('wtf - ' + reason);
          })
      }
    };
  });
    
  

See the Pen broken promise by Darrin Holst (@darrinholst) on CodePen.

If you’re unfamiliar with angular then the gist of it is that you have the LoginController that gets the login function called when the Login button is clicked. The controller uses a service called loginService (ignore that part about the service being defined with a factory call) that gets injected via the magical powers of angular. It uses that service to call an endpoint and returns a token on success and logs a message on failure. Back in the controller we set the background color to green on success and to red on failure. I know, right? That’s web 2.0 at its finest.

Assuming that the http call to whatevs fails; what do you expect to happen? As of before yesterday my guess would be the message would get logged in the service and then the controller would set the background to red.

It’d be a pretty boring post if that is what actually happened so go ahead and click Login below to see for yourself.

See the Pen broken promise by Darrin Holst (@darrinholst) on CodePen.

Green, amirite?

We can see from the console that we got the error logged.

console

The problem ends up being in our catch in the service. If you think of it like a traditional try/catch in other languages it makes sense. We caught the “exception”, but we didn’t “rethrow” it. The fix is to return a rejected promise in our error handling so the rejection gets propagated up the promise chain.

Notice the addition of the return $q.reject(reason); line in the service ($q is angular’s promise implementation).

    
angular.module('demo', [])

  .controller('LoginController', function(loginService) {
    var vm = this;

    vm.login = function login() {
      loginService.login(vm.username, vm.password)
        .then(function () {
          setBackground('green');
        })
        .catch(function () {
          setBackground('red');
        });
    }

    function setBackground(color) {
      angular.element(document.body).css({'background-color': color});
    }
  })

  .factory('loginService', function ($http, $q) {
    return {
      login: function login(username, password) {
        return $http.post('whatevs', {u: username, p: password})
          .then(function (response) {
            return response.data.token;
          })
          .catch(function (reason) {
            console.log('wtf - ' + reason);
            return $q.reject(reason);
          })
      }
    };
  });
    
  

See the Pen rejection by Darrin Holst (@darrinholst) on CodePen.

Try ‘er again…

See the Pen rejection by Darrin Holst (@darrinholst) on CodePen.

Like most everything else, it makes sense once you know how it works and is completely baffling when you don’t. #themoreyouknow

Check out this excellent post to get a better understanding of promises if you don’t already know all the things. In particular, I found this very helpful…

Every promise gives you a then() method (or catch(), which is just sugar for then(null, …)). Here we are inside of a then() function:

somePromise().then(function () {
    // I’m inside a then() function!
});

What can we do here? There are three things:

  1. return another promise
  2. return a synchronous value (or undefined)
  3. throw a synchronous error

That’s it. Once you understand this trick, you understand promises.