Promise in javascript is an object while callback is a function. A promise represents the outcome of a process that hasn’t yet completed while a callback is the function to call when a process completes in the future.
We used callback function to perform a task as soon as an asynchronous task is finished. To illustrate, let’s create a function that mimics an Ajax request to a server.
//mimics an ajax request with callback
var fakeAjaxCall = function(success, result){
setTimeout(function(){
success(result);
}, 2000);
};
Based from the fakeAjaxCall
above, it’s using success
as the callback function and assumes that it will at least take 2 secs to complete the Ajax call to the server.
Using the fakeAjaxCall, let’s do a fake Ajax request on an imaginary shopping cart to get the total amount. Our process starts with a user id from the page. We create a fake ajax request using the user id as a request parameter. To make the process looks a little bit real, we then multiply user id to 21 to get the cart total.
var user_id = 1;
console.log('user_id: '+ user_id);
fakeAjaxCall(function(res){
var cart_total = res * 21; //on real-life, this is a query result from the server using res as the request parameter
console.log('Your cart total: $'+ cart_total);
}, user_id)
Running the code on the terminal with Nodejs will output this:
This is simple and great. However, this will become cumbersome if we need to perform multiple ajax requests and the order of these requests matter. I bet you encountered this scenario before where you need to call a callback within a callback.
Let’s revise our example before just like in the real-world doing multiple consecutive ajax requests.
var user_id = 2;
console.log('user_id: '+ user_id);
fakeAjaxCall(function(res){
var cart_id = res * 21; //on real-life, this is a query result from the server using res as the request parameter
console.log('cart_id: '+ cart_id);
fakeAjaxCall(function(res){
var order_id = res * 21; //using the cart id, we get the order id
console.log('order_id: '+ order_id);
fakeAjaxCall(function(res){
var cart_total = res * 21; //using the order id, we calculate the total amount
console.log('Your cart total: $'+ cart_total);
}, order_id)
}, cart_id)
}, user_id)
Running the code above will output on the terminal:
To get the cart total, we need to have the user id, cart id, and order id first. To get the cart id, we need to do an an ajax request using the user id as the request parameter first. Then to retrieve the order id, we need to perform another ajax request using the just returned cart id as the request parameter.
If you notice, our code becomes harder to read and maintain. You might heard this pattern before - Callback Hell.
I believe we can make our code a lot better by using promise instead.
Let’s start by adding a function that will return a promise object that wraps our fakeAjaxCall
function.
var ajaxPromise = function(result, msg){
return new Promise(function(resolve, reject) {
result = result * 21;
console.log(msg + result);
fakeAjaxCall(resolve, result);
});
};
Take note on how we pass the promise’s resolve
function on our fakeAjaxCall
.
Emulating the multiple ajax request from the callback example, we can compose our promises object using its then
function.
var ajaxPromise = function(result, msg){
return new Promise(function(resolve, reject) {
result = result * 21;
console.log(msg + result);
fakeAjaxCall(resolve, result);
});
},
user_id = 2,
msg = 'cart_id: ';
console.log('user_id: '+ user_id);
//chain promises for multiple consecutive fake http requests
ajaxPromise(user_id, msg)
.then(function(result) {
msg = 'order_id: ';
return ajaxPromise(result, msg);
}).then(function(result) {
msg = 'cart total: $';
return ajaxPromise(result, msg);
});
Running this on the terminal will yield the same result as with using callback. However, our current code is more tersed, readable and maintainable.
Another cool thing is we can add a global exception handler to catch error anywhere on the promise chain.
var ajaxPromise = function(result, msg){
return new Promise(function(resolve, reject) {
result = result * 21;
console.log(msg + result);
fakeAjaxCall(resolve, result);
});
},
user_id = 2,
msg = 'cart_id: ';
console.log('user_id: '+ user_id);
//chain promises for multiple consecutive fake http requests
ajaxPromise(user_id, msg)
.then(function(result) {
msg = 'order_id: ';
return ajaxPromise(result, msg);
}).then(function(result) {
msg = 'cart total: $';
return ajaxPromise(result, msg);
})
["catch"](function(errorResult){
console.log(errorResult)
});