Javascript Promises are quite simple to start with, but confusions arise when chained promises are used :
- Why is then() executed after catch() in a sequence like then().catch().then() ?
- How can a catch() be assigned to each then(), something like then().catch().then().catch() ?
- Should a single catch() be used for all the then's() ?
Both then() and catch() Return a Promise
To understand promise chaining and error handling, it is important to know that both then() and catch() return a Promise.
Return Value of then()
When a Promise is resolved, the handler function in then() is executed. The value passed by resolve() is passed as a parameter to the handler.
var promise = new Promise(function(resolve, reject) {
resolve(55);
});
// callback in then() is executed when promise is resolved
promise.then(function(data) {
// 55 is the output
console.log(data);
});
The return value of then() is also a Promise, which is different from the original Promise :
If the handler function in then() returns a value, the Promise returned by then() is resolved with the returned value as its value.
var promise = new Promise(function(resolve, reject) { resolve(55); }); // handler in then() returns a value // then() returns a Promise var promise2 = promise.then(function(data) { return data; }); // promise2 is resolved promise2.then(function(data) { // 55 is the output console.log(data); });
If the handler function in then() throws an error, the Promise returned by then() is rejected with the returned error as its value.
var promise = new Promise(function(resolve, reject) { resolve(55); }); // handler in then() throws an error // then() returns a Promise var promise2 = promise.then(function(data) { throw new Error(100); }); // handler will never be called promise2.then(function(data) { console.log(data); }); // promise2 is rejected promise2.catch(function(data) { // Error object is the output console.log(data); });
If the handler function in then() returns an already resolved promise, the Promise returned by then() is resolved with that Promise's return value.
var promise = new Promise(function(resolve, reject) { resolve(55); }); // handler in then() returns a Promise // then() returns a different Promise var promise2 = promise.then(function(data) { return Promise.resolve(20); }); // promise2 is resolved promise2.then(function(data) { // 20 is the output console.log(data); });
Similarly if the handler function returns an already rejected promise, the Promise returned by then() is rejected with that Promise's return value.
If the handler function returns a pending Promise, the Promise returned by then() is resolved or rejected depending upon the returned Promise being resolved or rejected.
var promise = new Promise(function(resolve, reject) { resolve(55); }); // handler in then() returns a Promise // then() returns a different Promise var promise2 = promise.then(function(data) { var p = new Promise(function(resolve, reject) { setTimeout(function() { resolve(75); }, 1000); }); return p; }); // promise2 is resolved promise2.then(function(data) { // 75 is the output console.log(data); });
If the handler function returns nothing, the Promise returned by then() is resolved with undefined as its value.
var promise = new Promise(function(resolve, reject) { resolve(55); }); // handler in then() returns nothing // then() returns a Promise var promise2 = promise.then(function(data) { // no return value console.log(data); }); // promise2 is resolved promise2.then(function(data) { // undefined is the output console.log(data); });
Return Value of catch()
Since catch(onRejected) is just a wrapper for then(undefined, onRejected), return value of catch() is the same as described for then() in the above section. However for the sake for completeness, it is explained below.
When a Promise is rejected, the handler function in catch() is executed. The value passed by reject() is passed as a parameter to the handler.
var promise = new Promise(function(resolve, reject) {
reject(55);
});
// callback in catch() is executed when promise is rejected
promise.catch(function(data) {
// 55 is the output
console.log(data);
});
The return value of catch() is also a Promise, which is different from the original Promise :
If the handler function in catch() returns a value, the Promise returned by catch() is resolved with the returned value as its value.
var promise = new Promise(function(resolve, reject) { reject(55); }); // handler in catch() returns a value // catch() returns a Promise var promise2 = promise.catch(function(data) { return data; }); // promise2 is resolved promise2.then(function(data) { // 55 is the output console.log(data); });
If the handler function in catch() throws an error, the Promise returned by catch() is rejected with the returned error as its value.
var promise = new Promise(function(resolve, reject) { reject(55); }); // handler in catch() throws an error // catch() returns a Promise var promise2 = promise.catch(function(data) { throw new Error(100); }); // handler will never be called promise2.then(function(data) { console.log(data); }); // promise2 is rejected promise2.catch(function(data) { // Error object is the output console.log(data); });
If the handler function returns an already resolved promise, the Promise returned by catch() is resolved with that Promise's return value.
var promise = new Promise(function(resolve, reject) { reject(55); }); // handler in catch() returns a Promise // catch() returns a different Promise var promise2 = promise.catch(function(data) { return Promise.resolve(20); }); // promise2 is resolved promise2.then(function(data) { // 20 is the output console.log(data); });
Similarly if the handler function returns an already rejected promise, the Promise returned by catch() is rejected with that Promise's return value.
If the handler function returns a pending Promise, the Promise returned by catch() is resolved or rejected depending upon the returned Promise being resolved or rejected.
var promise = new Promise(function(resolve, reject) { reject(55); }); // handler in catch() returns a Promise // catch() returns a different Promise var promise2 = promise.catch(function(data) { var p = new Promise(function(resolve, reject) { setTimeout(function() { resolve(75); }, 1000); }); return p; }); // promise2 is resolved promise2.then(function(data) { // 75 is the output console.log(data); });
If the handler function returns nothing, the Promise returned by catch() is resolved with undefined as its value.
var promise = new Promise(function(resolve, reject) { reject(55); }); // handler in catch() returns nothing // catch() returns a Promise var promise2 = promise.catch(function(data) { // no return value console.log(data); }); // promise2 is resolved promise2.then(function(data) { // undefined is the output console.log(data); });
Example #1
If you have understood the above sections, managing a chain of promises should now be easier.
var promise = new Promise(function(resolve, reject) {
resolve(55);
});
promise.
then(function(data) {
console.log(data);
var p = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(75);
}, 1000);
});
return p;
}).
then(function(data) {
console.log(data);
return data + 20;
}).
then(function(data) {
console.log(data);
});
// output
55
75
95
Promise Rejections are Handled by the Next Available catch() in Line
Whenever a Promise is rejected in a chain, it skips forward to the next available catch(). This works just like handling a catch while writing synchronous code.
var promise = new Promise(function(resolve, reject) {
reject(55);
});
promise.
// skipped
then(function(data) {
console.log(data);
var p = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(75);
}, 1000);
});
return p;
}).
// skipped
then(function(data) {
console.log(data);
return data + 20;
}).
// executed
catch(function(data) {
// 55 is the output
console.log(data);
}).
// executed after catch()
then(function() {
// end is the output
console.log('end');
});
Example #2
var promise = new Promise(function(resolve, reject) {
reject(55);
});
promise.
then(function(data) {
console.log(data);
var p = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(75);
}, 1000);
});
return p;
}).
catch(function(data) {
console.log(data);
var p = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(45);
}, 1000);
});
return p;
}).
then(function(data) {
console.log(data);
throw new Error(data);
}).
then(function(data) {
console.log(data);
}).
catch(function(data) {
console.log(data);
return Promise.resolve(22);
}).
then(function(data) {
console.log(data);
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject(65);
}, 1000);
});
}).
catch(function(data) {
console.log(data);
return 100;
}).
then(function(data) {
console.log(data)
});
// output
55
45
Error
22
65
100
Conclusion
Understanding that both then() and catch() return a Promise which will be resolved or rejected, which will then call the next then() or catch() is the key to understanding Promise chaining.