Lesson ১৪৮ মিনিট পড়া

অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং (Async Programming)

সময়সাপেক্ষ কাজগুলোকে সামলান — যেমন ডেটা ফেচ (fetch) করা — আপনার অ্যাপকে ফ্রিজ না করেই

সমস্যা: জাভাস্ক্রিপ্ট হলো সিঙ্গেল-থ্রেডেড (The Problem: JavaScript Is Single-Threaded)

জাভাস্ক্রিপ্ট একটি সিঙ্গেল-থ্রেডে বা এক লাইন ধরে কাজ করে — অর্থাৎ এটি একবারে কেবল একটি কাজই করতে পারে। কিন্তু যদি কোনো কাজে অনেক সময় লাগে, যেমন ধরুন সার্ভার থেকে ডেটা আনা, কোনো ফাইল পড়া বা টাইমারের জন্য অপেক্ষা করা — তখন কী হবে? জাভাস্ক্রিপ্ট যদি প্রতিটি কাজের জন্য বসে বসে অপেক্ষা করতে থাকে, তবে আপনার পুরো অ্যাপটিই হ্যাঙ বা ফ্রিজ (freeze) হয়ে যাবে।

এর সমাধান কী? অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং (Asynchronous programming)। বসে বসে অপেক্ষা করার বদলে জাভাস্ক্রিপ্ট বলে, "কাজটি শুরু করে দাও, আর শেষ হলে আমাকে জানিও — ততক্ষণে আমি অন্য কাজগুলো করতে থাকি।" ব্যাপারটি অনেকটা চায়ের দোকানে চা অর্ডার করার মতো — আপনি নিশ্চয়ই শুধু খাবারের অপেক্ষায় হেঁশেলের দরজায় দাঁড়িয়ে থাকেন না। আপনি বরং আপনার টেবিলে বসে বন্ধুদের সাথে গল্পগুজব করেন, আর চা তৈরি হয়ে গেলে দোকানদার বা বয় তা আপনার টেবিলে এনে দেয়।

জাভাস্ক্রিপ্টে অ্যাসিন্ক বা async-এর বিবর্তন কয়েক বছর ধরেই হয়েছে: কলব্যাক (callbacks) (পুরোনো উপায়) → প্রমিস (Promises) (আগের চেয়ে ভালো উপায়) → async/await (সবচেয়ে পরিষ্কার ও আধুনিক উপায়)।

Click chart to zoom
ইভেন্ট লুপ (Event loop) যেভাবে অ্যাসিঙ্ক্রোনাস (async) কাজগুলো সামলায়: মেইন থ্রেড (main thread) ওয়েব এপিআই-এর (Web API) ওপর কাজের ভার দিয়ে নিজের অন্য কাজগুলো চালিয়ে যায়, আর কল স্ট্যাক (call stack) খালি হলে ইভেন্ট লুপ পুনরায় কলব্যাকগুলোকে (callbacks) এতে ফিরিয়ে নিয়ে আসে।

কলব্যাক — বহুল প্রচলিত উপায় (Callbacks — The Original Approach)

// setTimeout — the simplest async operation
console.log("1. Start");
setTimeout(() => {
console.log("2. This runs after 1 second");
}, 1000);
console.log("3. End (runs BEFORE the timeout!)");
// Callback pattern — pass a function to run when done
function fetchUser(id, callback) {
setTimeout(() => {
const user = { id, name: "Laboni", level: 42 };
callback(user);
}, 500);
}
fetchUser(1, (user) => {
console.log(`Got user: ${user.name}`);
});
// Callback hell — nested callbacks get ugly fast
// fetchUser(1, (user) => {
// fetchPosts(user.id, (posts) => {
// fetchComments(posts[0].id, (comments) => {
// // 😱 This is called "callback hell" or "pyramid of doom"
// });
// });
// });
Output
1. Start
3. End (runs BEFORE the timeout!)
Got user: Laboni
2. This runs after 1 second

প্রমিস — একটি অপেক্ষাকৃত ভালো উপায় (Promises — A Better Way)

একটি প্রমিস (Promise) হলো এমন একটি অবজেক্ট যা এমন একটি ভ্যালুর প্রতিনিধিত্ব করে যার অস্তিত্ব এখনও তৈরি হয়নি। এটিকে একটি গিফট কার্ডের সাথে তুলনা করতে পারেন — যা ভবিষ্যতে আপনাকে কিছু দেওয়ার একটি প্রতিশ্রুতি (promise) দেয়। একটি প্রামিস তিনটি অবস্থার যেকোনো একটিতে থাকতে পারে:

  • পেন্ডিং বা অপেক্ষমাণ (Pending) — কাজ বা অপারেশনটি এখনও চলছে
  • ফুলফিলড বা সম্পন্ন (Fulfilled) — কাজটি সফলভাবে শেষ হয়েছে (আপনি চাইলে এখন ফলাফলটি দেখতে পারেন)
  • রিজেক্টেড বা বাতিল (Rejected) — কাজটি ব্যর্থ হয়েছে (আপনি এররটি বা ভুলটি দেখতে পারেন)

সফল হলে সেটি হ্যান্ডল করার জন্য আপনি .then() ব্যবহার করতে পারেন এবং এরর বা ভুলের ক্ষেত্রে .catch() ব্যবহার করতে পারেন। সবচেয়ে মজার ব্যাপার হলো, প্রমিসগুলোকে চেইন (chained) বা একসাথে জোড়া দিয়ে যুক্ত করা যায়, ফলে আপনি ওই নেস্টেড কলব্যাকের (nested callback) দুঃস্বপ্ন থেকে বেঁচে যান।

প্রমিস তৈরি এবং চেইন করা (Creating & Chaining Promises)

// Creating a Promise
function delay(ms) {
return new Promise((resolve) => {
setTimeout(() => resolve(`Done after ${ms}ms`), ms);
});
}
delay(1000).then(msg => console.log(msg));
// A Promise that can fail
function fetchScore(playerId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (playerId > 0) {
resolve({ playerId, score: 9500 });
} else {
reject(new Error("Invalid player ID"));
}
}, 300);
});
}
// .then() for success, .catch() for errors
fetchScore(1)
.then(data => console.log(`Score: ${data.score}`))
.catch(err => console.log(`Error: ${err.message}`));
fetchScore(-1)
.then(data => console.log(`Score: ${data.score}`))
.catch(err => console.log(`Error: ${err.message}`));
Output
Done after 1000ms
Score: 9500
Error: Invalid player ID

Async/Await — আধুনিক পদ্ধতি (Async/Await — The Modern Way)

async/await মূলত প্রমিসের ওপর তৈরি একটি সিনট্যাকটিক সুগার (syntactic sugar), যা অ্যাসিন্ক বা async কোডগুলোকে দেখতে এবং কাজ করতে অনেকটাই সিঙ্ক্রোনাস (synchronous) কোডের মতো মনে করিয়ে দেয়। একটি async ফাংশন সবসময় একটি প্রমিস রিটার্ন করে। এর ভেতরে আপনি await ব্যবহার করে কোডের এক্সিকিউশন বা চলা আপাতত থামিয়ে রাখতে পারেন, যতক্ষণ পর্যন্ত না প্রমিসটি সম্পন্ন হয় বা সেটল (settle) করে।

এটিকে চায়ের দোকানের সেই উদাহরণের মতো করে ভাবলে এমনটা দাঁড়ায়: "আমি আমার সিঙ্গারার জন্য অপেক্ষা করছি (await)। এরপর আমি আমার চায়ের জন্য অপেক্ষা করি (await)। তারপর আমি বিলের জন্য অপেক্ষা করি (await)।" প্রতিটি ধাপ আগের ধাপটি শেষ হওয়ার জন্য অপেক্ষা করে, কিন্তু দোকানের অন্যান্য কাজ তার নিজস্ব গতিতেই চলতে থাকে।

Async/Await এবং Fetch API

// async/await — clean and readable
async function getPlayerInfo(id) {
try {
const response = await fetch(`https://api.example.com/players/${id}`);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const player = await response.json();
console.log(`Player: ${player.name}, Level: ${player.level}`);
return player;
} catch (error) {
console.log(`Failed to fetch player: ${error.message}`);
return null;
}
}
// Call it
getPlayerInfo(42);
// Promise.all — run multiple async tasks in PARALLEL
async function loadDashboard() {
try {
const [users, posts, stats] = await Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json()),
fetch("/api/stats").then(r => r.json())
]);
console.log(`Loaded: ${users.length} users, ${posts.length} posts`);
return { users, posts, stats };
} catch (error) {
console.log("One of the requests failed!");
}
}
// Promise.allSettled — don't fail if ONE fails
async function loadData() {
const results = await Promise.allSettled([
fetch("/api/critical").then(r => r.json()),
fetch("/api/optional").then(r => r.json())
]);
results.forEach((result, i) => {
if (result.status === "fulfilled") {
console.log(`Request ${i}: Success`);
} else {
console.log(`Request ${i}: Failed — ${result.reason}`);
}
});
}
Output
Player: Laboni, Level: 42
Loaded: 50 users, 120 posts
Request 0: Success
Request 1: Failed — TypeError: Failed to fetch
Note: খুব সাধারণ একটি ভুল: অ্যাসিন্ক (async) কল করার আগে 'await' ব্যবহার করতে ভুলে যাওয়া। await ছাড়া আপনি তার ভ্যালু বা মানের বদলে মূলত প্রমিস (Promise) অবজেক্টটিকেই পেয়ে যাবেন — আর এর ফলে ডেটা তৈরি হওয়ার আগেই আপনার কোড অযথাই রান করে ফেলবে। যদি আপনি কখনো আপনার ফলাফলের জায়গায় [object Promise] দেখতে পান, তবে বুঝে নেবেন যে আপনি হয়তো কোথাও await দিতে ভুলে গেছেন!
চ্যালেঞ্জ

ছোট কুইজ

একটি async ফাংশন কি সবসময়ই কোনো কিছু রিটার্ন করে?

পড়া চালিয়ে যান

Modules & ImportsClosures & Scope