Creating a Promise, Chaining & Error Handling in javascript
Creating a Promise, Chaining & Error Handling in JavaScript
Hey everyone! Promise is my favorite topic of JavaScript and it has made our lives so much easy. In the last episode, we talked about how we can consume a promise. In this episode, we will be talking about how we can create our own promise!
We will see:
- How to create our own promise
- How to handle errors inside promises
- Live examples of Promise chaining
I bet you that after this blog, you would be able to create your own promise and consume it as well. Let's begin!
The E-commerce Example Setup
Supposed we are creating an e-commerce website. We have a cart with items:
const cart = ["shoes", "pants", "kurta"]; // Step 1: Create order with cart items -> returns orderId // Step 2: Proceed to payment with orderId
The createOrder API is an async operation that returns a promise. We consume it like this:
const promise = createOrder(cart);
promise.then(function(orderId) {
proceedToPayment(orderId);
});
But who writes this createOrder function? WE as developers! So now let's see how to CREATE a promise.
How to Create a Promise
This is the important part. Listen carefully!
function createOrder(cart) {
const pr = new Promise(function(resolve, reject) {
// Logic to create order goes here
});
return pr;
}
OUTPUT: Returns a Promise object
To create a promise:
- Use the new keyword with Promise constructor
- Pass a function with two parameters: resolve and reject
- These resolve and reject are functions given by JavaScript!
Now let's write the complete createOrder function:
function validateCart(cart) {
// Logic to validate cart
return true;
}
function createOrder(cart) {
const pr = new Promise(function(resolve, reject) {
// Step 1: Validate the cart
if (!validateCart(cart)) {
const err = new Error("Cart is not valid");
reject(err);
}
// Step 2: Logic for creating order
// DB call, API call, etc.
const orderId = "12345";
// Step 3: If successful, resolve the promise
if (orderId) {
resolve(orderId);
}
});
return pr;
}
OUTPUT: // If cart is valid: Promise resolves with orderId: "12345" // If cart is invalid: Promise rejects with Error: "Cart is not valid"
Key points:
- resolve(data) - Call this when operation is successful
- reject(error) - Call this when operation fails
- Promise can only be resolved OR rejected - not both!
- Promise can only be resolved ONCE - JavaScript guarantees this!
Consuming Our Promise
createOrder(cart)
.then(function(orderId) {
console.log(orderId);
});
OUTPUT: 12345
Simulating Async Behavior with setTimeout
In real world, async operations take time. Let's simulate a 5 second delay:
function createOrder(cart) {
const pr = new Promise(function(resolve, reject) {
if (!validateCart(cart)) {
const err = new Error("Cart is not valid");
reject(err);
}
const orderId = "12345";
// Simulate 5 second delay (like a real API call)
setTimeout(function() {
resolve(orderId);
}, 5000);
});
return pr;
}
// Consuming it
const promise = createOrder(cart);
console.log(promise);
promise.then(function(orderId) {
console.log(orderId);
});
OUTPUT:
Promise { [[PromiseState]]: "pending" }
// After 5 seconds...
12345
See? Initially the promise is pending, and after 5 seconds it gets resolved with the orderId!
Error Handling with .catch()
What happens if we reject the promise? Let's set validateCart to return false:
function validateCart(cart) {
return false; // Cart is invalid!
}
createOrder(cart)
.then(function(orderId) {
console.log(orderId);
});
OUTPUT: Uncaught Error: Cart is not valid // RED ERROR in browser console!
This is BAD! We should never let errors fail silently. We need to handle errors gracefully using .catch():
createOrder(cart)
.then(function(orderId) {
console.log(orderId);
})
.catch(function(err) {
console.log(err.message);
});
OUTPUT: Cart is not valid // No red error! Gracefully handled.
IMPORTANT: Always write a .catch() to handle errors gracefully!
Promise Chaining - The Real Power
.then() gets the result of the previous one.Now let's add proceedToPayment to the picture. This is also an async operation that returns a promise:
function proceedToPayment(orderId) {
return new Promise(function(resolve, reject) {
// Payment logic here
resolve("Payment Successful");
});
}
OUTPUT: Returns a Promise that resolves with "Payment Successful"
Now let's chain them together:
createOrder(cart)
.then(function(orderId) {
console.log(orderId);
return orderId;
})
.then(function(orderId) {
return proceedToPayment(orderId);
})
.then(function(paymentInfo) {
console.log(paymentInfo);
})
.catch(function(err) {
console.log(err.message);
});
OUTPUT: 12345 Payment Successful
IMPORTANT: Always Return in the Chain!
This is a VERY COMMON BUG that developers make!
When chaining promises, you MUST return data from each .then() to pass it down the chain:
// WRONG - Data won't flow down!
createOrder(cart)
.then(function(orderId) {
console.log(orderId);
// Missing return!
})
.then(function(orderId) {
// orderId will be undefined here!
return proceedToPayment(orderId);
});
OUTPUT: 12345 // proceedToPayment receives undefined!
// CORRECT - Always return!
createOrder(cart)
.then(function(orderId) {
console.log(orderId);
return orderId; // Pass it down!
})
.then(function(orderId) {
return proceedToPayment(orderId); // Return the promise!
})
.then(function(paymentInfo) {
console.log(paymentInfo);
});
OUTPUT: 12345 Payment Successful
Rule: Whatever you return from one .then() will be passed to the next .then() in the chain!
Where to Place .catch() in the Chain?
The position of .catch() matters!
Catch at the END - Handles errors from ALL steps above it:
createOrder(cart)
.then(function(orderId) {
return orderId;
})
.then(function(orderId) {
return proceedToPayment(orderId);
})
.then(function(paymentInfo) {
console.log(paymentInfo);
})
.catch(function(err) {
// Catches errors from ALL above steps!
console.log(err.message);
});
OUTPUT: // If createOrder fails: Cart is not valid // If proceedToPayment fails: Payment Failed
Catch in the MIDDLE - Only handles errors above it, chain continues below:
createOrder(cart)
.then(function(orderId) {
return orderId;
})
.catch(function(err) {
// Only catches createOrder errors!
console.log(err.message);
})
.then(function(orderId) {
return proceedToPayment(orderId);
})
.then(function(paymentInfo) {
console.log(paymentInfo);
});
OUTPUT: Cart is not valid Payment Successful // Payment still happens even if cart validation failed!
Anything AFTER .catch() will always be called, no matter what errors occurred above!
createOrder(cart)
.then(function(orderId) {
return orderId;
})
.catch(function(err) {
console.log(err.message);
})
.then(function() {
console.log("No matter what happens, I will definitely be called!");
});
OUTPUT: Cart is not valid No matter what happens, I will definitely be called!
Complete Promise Chain Example
const cart = ["shoes", "pants", "kurta"];
function validateCart(cart) {
return true;
}
function createOrder(cart) {
return new Promise(function(resolve, reject) {
if (!validateCart(cart)) {
const err = new Error("Cart is not valid");
reject(err);
}
const orderId = "12345";
setTimeout(function() {
resolve(orderId);
}, 2000);
});
}
function proceedToPayment(orderId) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("Payment Successful for Order: " + orderId);
}, 1000);
});
}
// Promise Chain
createOrder(cart)
.then(function(orderId) {
console.log("Order Created:", orderId);
return orderId;
})
.then(function(orderId) {
return proceedToPayment(orderId);
})
.then(function(paymentInfo) {
console.log(paymentInfo);
})
.catch(function(err) {
console.log(err.message);
});
OUTPUT: // After 2 seconds... Order Created: 12345 // After 1 more second... Payment Successful for Order: 12345
Arrow Function Syntax (Cleaner Code)
Some developers prefer arrow functions for cleaner code:
createOrder(cart)
.then(orderId => {
console.log("Order Created:", orderId);
return orderId;
})
.then(orderId => proceedToPayment(orderId))
.then(paymentInfo => console.log(paymentInfo))
.catch(err => console.log(err.message));
OUTPUT: Order Created: 12345 Payment Successful for Order: 12345
Quick Recap
Creating a Promise:
const promise = new Promise(function(resolve, reject) {
// resolve(data) - for success
// reject(error) - for failure
});
Key Points:
- resolve and reject are functions given by JavaScript
- Promise can only be resolved ONCE
- Promise can only be resolved OR rejected, not both
- Always return from .then() to pass data down the chain
- Use .catch() to handle errors gracefully
- .catch() handles all errors ABOVE it in the chain
- Code AFTER .catch() will always execute
Vocabulary for Interviews:
- We attach a callback function to promise (not pass)
- We attach a failure callback using .catch()
- Data flows DOWN the promise chain
What's Next?
In the next episode, we will see how to combine multiple promises together. What if we have two promises and want to call them simultaneously and combine the results? Promise.all, Promise.race, and more exciting stuff coming up!
Keep coding, keep learning!
Post a Comment