Promises in javascript
Promises in JavaScript - A Deep Dive
Hey everyone! Welcome back to another episode of learning JavaScript. Today we're diving deep into PROMISES - the most important topic you should know if you are a JavaScript developer. If you're giving a frontend interview, then definitely 100% there will be questions around promises.
I can tell you what promises are in just 5 to 10 minutes but no, I won't do that. We will be diving deep into promises and explore the beauty of it. I promise that you will fall in love with promises after reading this!
So listen to me very carefully - Promises are used to handle async operations in JavaScript.
The Problem: How We Used to Write Code with Callbacks
Supposed we are creating an e-commerce website like Amazon. We have a cart and two APIs:
const cart = ["shoes", "pants", "kurta"]; // API 1: Creates order and returns order ID createOrder(cart) => returns orderId // API 2: Proceeds to payment proceedToPayment(orderId)
These two APIs are asynchronous and dependent on each other - we can only proceed to payment AFTER the order is created.
How did we handle this using callbacks?
// The OLD callback way
createOrder(cart, function(orderId) {
proceedToPayment(orderId);
});
OUTPUT: Order Created: 12345 Proceeding to Payment...
What's the problem here? INVERSION OF CONTROL! ( You don’t decide when callback runs. )
We have given our callback function to createOrder API and we're just sitting back relaxed, blindly trusting that it will call our callback. But:
- What if it never calls our callback?
- What if it calls it twice?
- We have NO control over our code anymore!
This is not a confident way of writing code. So how do we solve this?
Enter Promises - The Beautiful Solution
With promises, we design our APIs differently. The createOrder API will NO LONGER take a callback function. Instead, it will return us a PROMISE.
const promise = createOrder(cart);
// promise = { data: undefined }
// After 5-6 seconds when async operation completes...
// promise = { data: orderDetails }
OUTPUT:
Initially: Promise { data: undefined }
After async completes: Promise { data: { orderId: 12345 } }
What is a promise? It's an empty object that will be filled with data later!
When JavaScript executes this line:
- createOrder returns a promise immediately
- Initially the promise has undefined data
- JavaScript continues executing other code (doesn't wait!)
- After 5-6 seconds, the promise gets filled with actual data
Now how do we use this data? We attach a callback using .then():
const promise = createOrder(cart);
promise.then(function(orderId) {
proceedToPayment(orderId);
});
OUTPUT: Order Created: 12345 Payment Proceeding... Payment Done!
Why is This Better Than Callbacks?
You might ask - "We're still using callbacks here! What's the improvement?"
My dear friends, this piece of code is LOT LOT LOT better! Here's why:
With Callbacks: We PASS the function to another API (we lose control)
With Promises: We ATTACH a function to a promise object (we keep control)
The difference between passing and attaching is huge!
- createOrder only does its job - creates order and fills the promise
- Once promise is filled, our callback is automatically called
- JavaScript GUARANTEES the callback will be called once data arrives
- It will be called just ONCE - never twice, never zero times
- We have control of our program with us!
Isn't it beautiful to have this guarantee and trust as a developer?
Let's See a Real Promise Object
Let's use the fetch function to see a real promise:
const user = fetch("https://api.github.com/users/shreyashkolhe");
console.log(user);
OUTPUT:
Promise {
[[PromiseState]]: "pending",
[[PromiseResult]]: undefined
}
The promise has two important properties:
- PromiseState - tells the state (pending, fulfilled, or rejected)
- PromiseResult - holds the actual data when resolved
After the async operation completes:
OUTPUT:
Promise {
[[PromiseState]]: "fulfilled",
[[PromiseResult]]: Response { body: ReadableStream, ... }
}
To use the data, we attach a callback with .then():
const user = fetch("https://api.github.com/users/shreyashkolhe");
user.then(function(data) {
console.log(data);
});
OUTPUT:
Response { body: ReadableStream, headers: Headers, ok: true, ... }
The Three States of a Promise
A promise can only have THREE states:
- Pending - Initial state, waiting for async operation
- Fulfilled - Operation completed successfully
- Rejected - Operation failed
That's it! No other state possible. This brings TRUST in our code.
Also, promise objects are IMMUTABLE. Once resolved, nobody can change the data. You can pass it anywhere in your code without worrying about mutation!
What is a Promise? (Interview Answer)
There are different definitions:
- A placeholder that will be filled with a value later
- A container for a future value
But the best definition from MDN:
"Promise is an object representing eventual completion of an asynchronous operation"
Let me break it down:
- Promise is an object
- It represents something
- Eventual completion - it will eventually complete
- Of an async operation
Say it with me: "Promise is an object representing eventual completion of an async operation"
If you say this to your interviewer, trust me - they won't ask anything more about promises!
Promise Chaining - Solving Callback Hell
Remember callback hell? Let's see how it looked:
// CALLBACK HELL - Code grows horizontally!
createOrder(cart, function(orderId) {
proceedToPayment(orderId, function(paymentInfo) {
showOrderSummary(paymentInfo, function(summary) {
updateWalletBalance(summary, function() {
// More nested callbacks...
});
});
});
});
OUTPUT: Order Created! Payment Done! Summary Shown! Wallet Updated! // But the code is UGLY and hard to maintain!
This is the Pyramid of Doom! Code grows horizontally and becomes unmaintainable.
With Promise Chaining, we can write this beautifully:
createOrder(cart)
.then(function(orderId) {
return proceedToPayment(orderId);
})
.then(function(paymentInfo) {
return showOrderSummary(paymentInfo);
})
.then(function(summary) {
return updateWalletBalance(summary);
});
OUTPUT: Order Created! Payment Done! Summary Shown! Wallet Updated! // Code is CLEAN and maintainable!
IMPORTANT: Always return the promise in each .then() so data flows through the chain!
Some developers prefer arrow functions for cleaner code:
createOrder(cart)
.then(orderId => proceedToPayment(orderId))
.then(paymentInfo => showOrderSummary(paymentInfo))
.then(summary => updateWalletBalance(summary));
OUTPUT: Order Created! Payment Done! Summary Shown! Wallet Updated! // Even more lean and beautiful!
See how the code grows VERTICALLY instead of horizontally? That's the beauty of promises!
Quick Recap
Problems with Callbacks:
- Inversion of Control - We lose control by passing functions to other APIs
- Callback Hell - Nested callbacks make code unmaintainable
How Promises Solve These:
- We attach callbacks to promise objects (not pass to other functions)
- Promise guarantees callback will be called once when data arrives
- Promises are immutable - data can't be changed
- Promise chaining keeps code clean and vertical
Promise States:
- Pending
- Fulfilled
- Rejected
Interview Definition:
"Promise is an object representing eventual completion of an asynchronous operation"
What's Next?
In this blog, we learned how to consume promises. But how do we create our own promises?
In the next episode, we'll see how to create a promise and return it from our APIs. We'll write our own implementation of createOrder API!
Keep coding, keep learning!
Post a Comment