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.
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:
- return another promise
- return a synchronous value (or undefined)
- throw a synchronous error
That’s it. Once you understand this trick, you understand promises.