Javascript Promises – Part II – Handling errors
In the previous article, we’ve learned what Promise
is and went through some basic use cases of Promise
. In this article, we will learn how to handle errors while using Promise
.
If you haven’t read part I, I would recommend that you take a look at it here.
If you’ve read part I, you may have wondered when the reject
function would be called and how to specify a real reject
function to the Promise
.
Actually, in the previous part, to make life simple, we’ve ignored the reject
function. The full version of .then()
should come with 2 parameter: resolve
and reject
function.
Reject function
To be clearer on this, let’s open our cool console tab in chrome and type the following code
var p1 = new Promise(function(resolve, reject) { console.log('1'); throw 'Uh-oh!'; }); p1;
The console outputs would be like below:
Promise { [[PromiseStatus]]: "rejected", [[PromiseValue]]: "Uh-oh!" }
Ok, something have changed. p1
‘s status is now "rejected"
and its PromiseValue
is now "Uh-oh!"
.
What happened was that during its execution, p1
met an error (exception) and it cannot be resolved, but was rejected. Thus its status is "rejected"
and the exception is saved in the PromiseValue
, so that it can be passed to the “reject function” if specified.
Now let’s try to specify the resolve function as we did in the previous part:
var p2 = p1.then(function(val) { console.log('2: ' + val); return val; }); p2;
The console outputs would be:
Promise { [[PromiseStatus]]: "rejected", [[PromiseValue]]: "Uh-oh!" }
p2 status is also "rejected"
and the PromiseValue
is forwarded to p2
. More over, we can see that the “resolve function” was not called since there was no console log output.
Let’s change p2
a little bit by adding a “reject function” to .then()
:
var p2 = p1.then( function(val) { console.log('resolve 1: ' + val); return val; }, function(err) { console.log('reject 1: ' + err); return err; } ); p2;
The console outputs would now be:
Promise { [[PromiseStatus]]: "resolved", [[PromiseValue]]: "Uh-oh!" }
p2
is now "resolved"
, the console logs "reject 1: Uh-oh!"
and the PromiseValue
is "Uh-oh!"
, which is because we called return err;
in the reject function.
In the code above, we pass 2 params to .then()
, the first one is the “resolve function” to call when p1
succeeds, the second one is the “reject function” to call when p1
fails. Since p1
is rejected, the reject function is called. There’s no error during execution of p2
so p2
status is resolved.
Now if we run the following code:
var p3 = p2.then( function(val) { console.log('resolve 2: ' + val); return val; }, function(err) { console.log('reject 2: ' + err); return err; } ); p3;
The console outputs would be as below:
Promise { [[PromiseStatus]]: "resolved", [[PromiseValue]]: "Uh-oh!" }
We can see that the “resolve function” was called and p3
is resolved .
then() and catch()
To make life even simpler and code even clearer, Promise provides us with another helpful function: catch()
Instead of writing this:
var p2 = p1.then( function(val) { console.log('resolve 1: ' + val); return val; }, function(err) { console.log('reject 1: ' + err); return err; } ); p2;
We can now write this:
var p2 = p1 .then(function(val) { console.log('resolve 1: ' + val); return val; }) .catch(function(err) { console.log('reject 1: ' + err); return err; }) p2;
Which will basically execute and output the same thing:
Promise { [[PromiseStatus]]: "resolved", [[PromiseValue]]: "Uh-oh!" }
Technically, the call to .catch(rejectFunction)
is equal to calling .then(undefined, rejectFunction)
and in the the above code, there was actually another Promise
in between, right after the first .then()
. So the above code is actually:
var p1_1 = p1 .then(function(val) { console.log('resolve 1: ' + val); return val; }); p1_1; var p2 = p1_1.catch(function(err) { console.log('reject 1: ' + err); return err; }) p2;
One good thing about Promise
is that if an error is not handled by a promise, it make the promise rejected and keep forwarding the error to the next promise. Therefore in the above code, p1
does not have a reject function so p1_1
‘s status is rejected and the error get passed to the next promise. Since p1_1
has a reject function (specified by .catch()
), p2
is resolved.
If we don’t use .catch()
, and specified the reject function inside of a .then()
, then we must specified the next nested layer of resolve and reject functions inside of the first reject function, and if this keeps going on, we would end up in the same callback hell as when we don’t use Promise
at all.
By using .catch()
, the error handling would also be much simpler when we chain promise after promise like this:
var p2 = p1 .then(resolveFunc1) .then(resolveFunc2) .then(resolveFunc3) .then(resolveFunc4) .catch(rejectFunc);
When we write like this, if error happens during any node of the promise chain, the rejectFunc
will be called.
We can also catch the error for a particular promise separately like this:
var p2 = p1 .then(resolveFunc1) .then(resolveFunc2) .catch(rejectFunc2) .then(resolveFunc3) .then(resolveFunc4) .catch(rejectFunc);
In this part, you’ve learned how to handle errors using reject function and .catch()
. We will discuss more on Promise chain in the next part of this blog series. That’s it for part II.
Next in series: Javascript Promises – Part III – Chaining Promise’s