Javascript Promises - Understanding Chaining and Error Handling

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.

I'm available for freelancing
If you've liked this article, you'll probably like my code too.

From short code snippets to complete web applications, I code in Javascript & PHP
Low Prices from $20, Fast Delivery Time
I'm available for freelancing
If you've liked this article, you'll probably like my code too.

From short code snippets to complete web applications, I code in Javascript & PHP
Low Prices from $20, Fast Delivery Time