Introduction
Promise is a very important concept of javascript when it comes to handling some asynchronous operations. In this blog, we will build a deep understanding of the promise by discussing its importance in our daily coding. we will also discuss some hands-on coding examples to develop a better understanding of promises.
Why Promise
Before diving deep into promises let's discuss why the promise is introduced in JavaScript.
Before using promises we were using callback functions for performing all the Asynchronous operations (like fetching data from the server) in javascript but using callbacks has two major drawbacks i.e
Callback Hell: nested callbacks (callback inside callback) makes a code unmaintainable and unreadable
Inversion of Control: we lose our control over code by using nested callbacks.
To know more about these two concepts with examples read my blog The Dark Side of Callbacks
What is promise
A promise is an object which is filled with data on the completion of some Async operation in JavaScript.
The promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
Mdn docs
Let's try to look at an example of fetching GitHub API which will return us a promise and see what a promise looks like.
const url = "https://api.github.com/users/abdulkadir18apr";
const promise = fetch(url);
console.log(promise)
In the above example, you can see a promise object with some key-value pair let's discuss it one by one
Promise State [[promiseState]]
promise state represents a current state of a promise and a promise can have only three states i.e
Pending: A promise remains pending until it is filled with data returned by some async operation it represents that it is neither rejected nor resolved.
resolved (fulfilled): A promise is resolved if the async operation is completed successfully.
rejected: A promise is rejected if the async operation returns some failure/error.
Promise Result [[promiseResult]]
promise results hold the data returned by an async operation like in our example of fetching a user of GitHub promise result holds a complete response that is returned by GitHub API.
Data Out of Promise
In the above example, we got a promise object but what if we want some data like the bio of any user from the response of an API?
To achieve this, we need a response from a GitHub API and which is an async operation that returns a promise once we got a promise we can attach a callback function to a promise that will execute once the promise is resolved and log the bio of the user from the response to the console.
Let's understand it with a code example
const url = "https://api.github.com/users/abdulkadir18apr";
const promise = fetch(url);
console.log(promise);
//callBack functions
const convertResponseIntoJson = (res) => res.json();
const getBioFromResponse = (data) => console.log(data.bio);
//attaching callBack with a promise
const jsonDataPromise = promise.then(convertResponseIntoJson);
console.log(jsonDataPromise);
jsonDataPromise.then(getBioFromResponse);
Output
Callback Hell to Promise Land
Does Promise solve the problem of callback Hell and Inversion of control? if yes then How?
In the callback, we were passing the callback function to some async operation and trusting that the async operation will call our function at some time but here we are not passing a callback function instead we are attaching a callback function to a promise of that async function using .then () method.
let's take the same e-commerce example to illustrate promises.
const createOrder = async(cart) => {
return new Promise((resolve, reject) => {
if (cart.length > 0) {
resolve({ orderId: Math.random() * 1000, msg: "order-created" })
}
else {
reject("Invalid orderId")
}
})
}
const proceedToPayment = async({ orderId }) => {
return new Promise((resolve, reject) => {
if (orderId > 0) {
resolve("proceed to payment success");
}
else {
reject("Failed");
}
})
}
//calling these function
let cart = ["laptop", "mouse", "keybord"];
let createOrderPromise = createOrder(cart) //return a promise
createOrderPromise.then((orderData) => {
console.log(orderData)
return proceedToPayment(orderData);
}).then((msg) => console.log(msg))
.catch((err)=>console.log(err));
Output:-
In the above example, our API's creteOrder and proceedTopayment are designed in a way that it returns promises like createOrder will return a promise with data of orderId once we receive data in the promise object we attach a function that will call proceedToPayment with orderId which in turn return a promise with the message "proceed to payment success". In case any API fails then we handle an error in the .catch() block.
Designing a createOrder and ProceedToPayment in that way solves the problem of callback hell and inversion of control as now code there are no nested callbacks instead we have promise chaining and control is with the developer as the callback function is attached to the promise means it depends on the data returned by the async function in promise instead of async function itself.
Summary
In this blog, we started with an understanding of the need for promises in JavaScript and discussed problems like callback hell and inversion of control while using a callback approach to perform async tasks.
further, we tried to build an understanding of promises with the example of fetching user data from the GitHub server bio GitHub API.
Finally, we discussed how the promise solved the problem of callback Hell and inversion of control with help of an e-commerce example.