Promises — From Callback Hell to Heaven
"The Future Value of JavaScript"
আগের অধ্যায়ে আমরা দেখেছি Callback Hell কীভাবে আমাদের কোডকে দুর্বোধ্য করে তোলে। ES6 (2015) আমাদের এই নরক থেকে উদ্ধার করতে নিয়ে এলো Promises।
একজন জুনিয়র ডেভেলপার Promise ব্যবহার করে শুধু .then() দিয়ে। কিন্তু একজন সিনিয়র ডেভেলপার জানে কিভাবে প্যারালাল রিকোয়েস্ট হ্যান্ডেল করতে হয়, রেইস কন্ডিশন ম্যানেজ করতে হয় এবং Microtask Queue অপটিমাইজ করতে হয়।
⭐ ১. Promise আসলে কী? (The Burger Analogy) 🍔
সহজ ভাষায়, Promise হলো এমন একটি অবজেক্ট যা ভবিষ্যতে কোনো একটি ভ্যালু দেওয়ার "প্রতিশ্রুতি" দেয়।
ধরো তুমি কেএফসিতে বার্গার অর্ডার দিলে।
- কাউন্টার থেকে তোমাকে একটি টোকেন দেওয়া হলো। (এটিই Promise)
- বার্গার তৈরি হচ্ছে। (অবস্থা: Pending)
- দুইটা ঘটনা ঘটতে পারে:
- বার্গার দিলে (Fulfilled / Resolved) ✅
- বার্গার হবে না (Rejected) ❌
Code Representation:
const burgerPromise = new Promise((resolve, reject) => {
const isReady = true;
if (isReady) {
resolve("Here is your Burger 🍔");
} else {
reject("Sorry, no chicken left ❌");
}
});
⭐ ২. Consuming Promises: Then, Catch, Finally
প্রমিজ তৈরি করার চেয়ে কনজিউম করা (ব্যবহার করা) বেশি জরুরি।
burgerPromise
.then((food) => {
console.log(food); // Success হলে এখানে আসবে
})
.catch((error) => {
console.error(error); // Error হলে এখানে আসবে
})
.finally(() => {
console.log("Go Home 🏠"); // রেজাল্ট যা-ই হোক, এটা চলবেই
});
⭐ ৩. Promise Chaining: The Pipeline ⛓️
প্রমিজের আসল ম্যাজিক হলো চেইনিং। আমরা একটার আউটপুট আরেকটার ইনপুট হিসেবে ব্যবহার করতে পারি।
Scenario: ইউজার ডাটা আনো ➡ তার আইডি দিয়ে পোস্ট আনো ➡ প্রথম পোস্টের কমেন্ট আনো।
getUser(101)
.then(user => getPosts(user.id)) // return new Promise
.then(posts => getComments(posts[0].id)) // return new Promise
.then(comments => console.log("Final Comments:", comments))
.catch(err => console.error("Something went wrong:", err));
Senior Insight:
.then() এর ভেতর থেকে যা কিছুই রিটার্ন করবে, সেটা অটোমেটিক পরবর্তী .then() এর ইনপুট হয়ে যাবে। যদি প্রমিজ রিটার্ন করো, তবে JS অপেক্ষা করবে সেটা resolve হওয়া পর্যন্ত।
⭐ ৪. Handling Multiple Promises (Concurrency) 🚀
রিয়েল লাইফে আমাদের একসাথে অনেকগুলো API কল করতে হয়। সিনিয়ররা জানেন কখন কোনটা ব্যবহার করতে হবে।
A) Promise.all([p1, p2, p3]) — All or Nothing
সবগুলো সফল হলেই কেবল রেজাল্ট পাবে। একটা ফেইল করলে পুরোটা রিজেক্ট।
Use Case: যখন সবগুলো ডেটা একে অপরের ওপর নির্ভরশীল।
B) Promise.allSettled([p1, p2, p3]) — The Reporter
সফল বা ব্যর্থ—সবগুলোর রিপোর্ট দেবে।
Use Case: ড্যাশবোর্ডে ৫টা চার্ট লোড হচ্ছে। একটা ফেইল করলেও বাকি ৪টা দেখানো উচিত।
C) Promise.race([p1, p2]) — The Sprinter
যে সবার আগে শেষ করবে (সফল বা ব্যর্থ), তার রেজাল্টই নেওয়া হবে।
Use Case: টাইমআউট সেট করা (৫ সেকেন্ডের মধ্যে ডেটা না আসলে এরর দেখাব)।
D) Promise.any([p1, p2]) — The Optimist
যে সবার আগে সফল (Resolve) হবে, তাকে নেবে। সব ফেইল করলে কেবল রিজেক্ট হবে।
Use Case: তোমার ৩টা সার্ভার (CDN) আছে। যেকোনো একটা থেকে ইমেজ লোড হলেই হলো।
📊 Comparison Table
| Method | Fails if... | Returns |
|---|---|---|
| all | ❌ ১টা ফেইল করলেই | সব ভ্যালুর Array |
| allSettled | কখনই না | স্ট্যাটাস ও ভ্যালুর Object Array |
| race | প্রথমজন ফেইল করলে | প্রথম রেজাল্ট (Success/Error) |
| any | সবগুলো ফেইল করলে | প্রথম Success ভ্যালু |
⭐ ৫. Advanced Patterns (Senior Must-Know)
🔥 Pattern 1: Retry Logic (নেটওয়ার্ক ফেইল করলে আবার চেষ্টা করা)
ইন্টারভিউতে বা রিয়েল অ্যাপে এটা খুব গুরুত্বপূর্ণ।
function fetchWithRetry(url, retries = 3) {
return fetch(url).catch(err => {
if (retries > 0) {
console.log(`Retrying... attempts left: ${retries}`);
return fetchWithRetry(url, retries - 1);
}
throw err; // সব চেষ্টা ব্যর্থ হলে এরর দাও
});
}
🔥 Pattern 2: Delay Utility (Promisification)
setTimeout কে প্রমিজে কনভার্ট করা।
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// Usage
wait(2000).then(() => console.log("2 seconds passed!"));
⭐ ৬. Microtask Queue Revisited
অধ্যায় ৮-এ আমরা দেখেছিলাম প্রমিজ VIP (Microtask)।
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// Output: 1 ➡ 4 ➡ 3 (Microtask আগে) ➡ 2 (Macrotask পরে)
⭐ ৭. Common Anti-Patterns (যা করবে না) 🚫
জুনিয়ররা প্রায়ই প্রমিজ ব্যবহার করতে গিয়ে Promise Hell তৈরি করে।
❌ Bad (Nested Chains - The Pyramid Revisited):
getData().then(data => {
getMore(data).then(more => {
getFinal(more).then(res => {
console.log(res);
});
});
});
✅ Good (Linear Chaining):
getData()
.then(data => getMore(data))
.then(more => getFinal(more))
.then(res => console.log(res))
.catch(err => console.error(err));
🧠 ৮. Senior Level Interview Questions
Q1: Promise.all এবং Promise.allSettled এর মূল পার্থক্য কী?
উত্তর: Promise.all "Short-circuit" করে (একটা ফেইল করলেই সব শেষ)। Promise.allSettled সবগুলোর রেজাল্ট (Success/Fail) আসার জন্য অপেক্ষা করে।
Q2: আমরা কি catch ব্লকের পরে আবার .then লিখতে পারি?
উত্তর: হ্যাঁ! catch নিজেও একটা প্রমিজ রিটার্ন করে। তাই এরর হ্যান্ডেল করার পরেও আমরা চেইন চালিয়ে যেতে পারি (একে "Recovering from Error" বলা হয়)।
Q3: Promise.resolve() কি সিঙ্ক্রোনাস না অ্যাসিনক্রোনাস?
উত্তর: অ্যাসিনক্রোনাস। এটি Microtask Queue-তে চলে যায়।
🚀 অধ্যায় ৯ সারাংশ (Checklist)
আমরা শিখলাম:
- ✓ Status: Pending, Fulfilled, Rejected
- ✓ Flow: .then সবসময় নতুন প্রমিজ রিটার্ন করে, তাই চেইন করা যায়
- ✓ Concurrency: all (সব চাই), allSettled (রিপোর্ট চাই), race (দ্রুততম চাই), any (যেকোনো একটা সফল চাই)
- ✓ Patterns: Retry Logic এবং Promisification
- ✓ Priority: প্রমিজ সবসময় setTimeout এর আগে রান হয়
Next Step: প্রমিজ চেইনিং অনেক সুন্দর, কিন্তু এর চেয়েও সুন্দর এবং ক্লিন সিনট্যাক্স হলো Async/Await। এটি আমাদের অ্যাসিনক্রোনাস কোডকে দেখতে সিঙ্ক্রোনাস কোডের মতো করে দেয়। পরের অধ্যায়: "Async/Await — Synchronous Look for Async Code"।