Advance JavaScript শিখুন বাংলায় (পর্ব ৬) — Promise বিস্তারিত (all, race, allSettled) শিখুন

Promise বিস্তারিত - all, race, allSettled

আগের পর্বে আমরা Spread ও Rest Operator শিখেছিলাম। এই পর্বে আমরা দেখব Promise বিস্তারিতভাবে — শুধু বেসিক .then() না, বরং Promise.all, Promise.race, আর Promise.allSettled দিয়ে একাধিক অ্যাসিনক্রোনাস কাজ একসাথে সামলানো।

Promise রিক্যাপ

একটা Promise হলো একটা অ্যাসিনক্রোনাস কাজের ভবিষ্যৎ ফলাফলের "প্রতিশ্রুতি" — এটা তিনটা অবস্থায় থাকতে পারে: pending (চলছে), fulfilled (সফল), আর rejected (ব্যর্থ)।

function fetchUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) {
        resolve({ id, name: "আরিফ" });
      } else {
        reject(new Error("ভুল ID"));
      }
    }, 1000);
  });
}

// async/await দিয়ে ব্যবহার
async function loadUser() {
  try {
    const user = await fetchUser(1);
    console.log(user);
  } catch (error) {
    console.log("এরর:", error.message);
  }
}

সমস্যা — একাধিক Promise একে একে চালানো ধীরগতির

ধরো তোমাকে ৩টা আলাদা API কল করতে হবে, যেগুলো একে অপরের উপর নির্ভরশীল না। যদি একে একে await করো, প্রতিটার জন্য অপেক্ষা করতে হবে — মোট সময় লাগবে সবগুলোর যোগফল।

// ❌ ধীর — একে একে চলে, মোট সময় = 1s + 1s + 1s = 3s
async function loadAllSlow() {
  const user = await fetchUser(1); // 1 সেকেন্ড অপেক্ষা
  const posts = await fetchPosts(1); // আরও 1 সেকেন্ড অপেক্ষা
  const comments = await fetchComments(1); // আরও 1 সেকেন্ড অপেক্ষা
  return { user, posts, comments };
}

Promise.all — সব একসাথে, সবগুলো লাগবে

Promise.all() একাধিক Promise একসাথে (parallel) শুরু করে, সবগুলো শেষ হলে একটা অ্যারে হিসেবে ফলাফল দেয়। মোট সময় লাগে সবচেয়ে ধীর কাজটার সমান — আলাদা আলাদা যোগফল না।

// ✅ দ্রুত — সব একসাথে চলে, মোট সময় ≈ 1s (সবচেয়ে ধীরটার সমান)
async function loadAllFast() {
  const [user, posts, comments] = await Promise.all([
    fetchUser(1),
    fetchPosts(1),
    fetchComments(1)
  ]);
  return { user, posts, comments };
}

সতর্কতা: Promise.all-এ যেকোনো একটা Promise ব্যর্থ (reject) হলে, সাথে সাথে পুরো Promise.all ব্যর্থ হয়ে যায় — বাকিগুলো সফল হলেও তাদের ফলাফল পাওয়া যায় না ("fail fast" আচরণ)।

লাইভ ডেমো — একে একে বনাম একসাথে

নিচের বাটনে চাপো — একই তিনটা কাজ (প্রতিটা ~৮০০ms) একবার একে একে, একবার একসাথে চালিয়ে সময়ের পার্থক্য দেখো।

Promise.race — যেটা আগে শেষ হয়

Promise.race() একাধিক Promise একসাথে চালায়, কিন্তু শুধু যেটা সবার আগে সেটেল (সফল বা ব্যর্থ) হয়, সেটার ফলাফল নেয়। বাকিগুলো চলতেই থাকে, কিন্তু তাদের ফলাফল উপেক্ষা করা হয়।

const fastServer = new Promise((resolve) => setTimeout(() => resolve("দ্রুত সার্ভার"), 500));
const slowServer = new Promise((resolve) => setTimeout(() => resolve("ধীর সার্ভার"), 2000));

const result = await Promise.race([fastServer, slowServer]);
console.log(result); // "দ্রুত সার্ভার" — যেটা আগে শেষ হয়েছে

সবচেয়ে দরকারি ব্যবহার হলো টাইমআউট বানানো — একটা API কল যদি নির্দিষ্ট সময়ের মধ্যে শেষ না হয়, তাহলে একে জোর করে "ব্যর্থ" ধরে নেওয়া:

function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error("টাইমআউট!")), ms)
  );
  return Promise.race([promise, timeout]);
}

try {
  const data = await withTimeout(fetchUser(1), 3000); // ৩ সেকেন্ডের বেশি লাগলে এরর
  console.log(data);
} catch (error) {
  console.log(error.message); // "টাইমআউট!" হতে পারে
}

Promise.allSettled — সব ফলাফল, ব্যর্থ হলেও

Promise.all-এর সমস্যা ছিল একটা ব্যর্থ হলেই পুরোটা ব্যর্থ। Promise.allSettled() এই সমস্যা এড়ায় — এটা সব Promise শেষ হওয়া পর্যন্ত অপেক্ষা করে, প্রতিটার ফলাফল (সফল বা ব্যর্থ) আলাদাভাবে জানায়।

const results = await Promise.allSettled([
  fetchUser(1),
  fetchUser(-1), // এটা reject হবে (ভুল ID)
  fetchUser(2)
]);

console.log(results);
// [
//   { status: "fulfilled", value: { id: 1, name: "আরিফ" } },
//   { status: "rejected", reason: Error("ভুল ID") },
//   { status: "fulfilled", value: { id: 2, name: "..." } }
// ]

// সফল আর ব্যর্থগুলো আলাদা করা
const succeeded = results.filter(r => r.status === "fulfilled").map(r => r.value);
const failed = results.filter(r => r.status === "rejected");

console.log(`${succeeded.length}টা সফল, ${failed.length}টা ব্যর্থ`);

কখন ব্যবহার করবে: যখন একটা কাজ ব্যর্থ হলেও বাকিগুলোর ফলাফল দরকার — যেমন একসাথে ১০টা ছবি আপলোড করা, যেখানে ১টা ব্যর্থ হলেও বাকি ৯টা সফল হওয়াটা গুরুত্বপূর্ণ।

এক নজরে — কখন কোনটা

মেথড কখন ব্যবহার করবে
Promise.all সবগুলো সফল হওয়া লাগবেই — যেকোনো একটা ব্যর্থ হলে পুরোটাই বাতিল
Promise.race শুধু প্রথম ফলাফল দরকার (টাইমআউট, দ্রুততম সার্ভার)
Promise.allSettled প্রতিটার ফলাফল আলাদাভাবে দরকার, কিছু ব্যর্থ হলেও সমস্যা নেই

একটা সম্পূর্ণ উদাহরণ — একাধিক বই একসাথে ডাউনলোড করা

চলো Pathok Ghor-এর একটা বাস্তব দৃশ্যকল্প দেখি — ব্যবহারকারী একসাথে কয়েকটা বই ডাউনলোডের জন্য সিলেক্ট করেছে, কিছু ব্যর্থ হলেও যেগুলো সফল হয়েছে সেগুলো জানাতে হবে।

async function downloadBook(bookId) {
  const response = await fetch(`/api/books/${bookId}/download`);
  if (!response.ok) {
    throw new Error(`বই ${bookId} ডাউনলোড ব্যর্থ`);
  }
  return response.blob();
}

async function downloadSelectedBooks(bookIds) {
  const results = await Promise.allSettled(
    bookIds.map((id) => downloadBook(id))
  );

  const succeeded = [];
  const failed = [];

  results.forEach((result, index) => {
    if (result.status === "fulfilled") {
      succeeded.push(bookIds[index]);
    } else {
      failed.push(bookIds[index]);
    }
  });

  if (succeeded.length > 0) {
    console.log(`✅ সফল: ${succeeded.length}টা বই`);
  }
  if (failed.length > 0) {
    console.log(`❌ ব্যর্থ: বই আইডি ${failed.join(", ")}`);
  }

  return { succeeded, failed };
}

downloadSelectedBooks(["book1", "book2", "book3"]);

এখানে Promise.allSettled ব্যবহার করার কারণ পরিষ্কার — একটা বই ডাউনলোড ব্যর্থ হলে ব্যবহারকারীর বাকি সফল ডাউনলোডগুলো হারানো উচিত না, যেটা Promise.all ব্যবহার করলে হয়ে যেত।

নিজে চেষ্টা করো

Exercise:

  1. setTimeout দিয়ে তিনটা ভিন্ন সময়ের ফেক Promise বানাও, তারপর Promise.race দিয়ে দেখো কোনটা আগে শেষ হয়।
  2. উপরের withTimeout ফাংশনটা নিজে বানাও, তারপর একটা ধীর Promise-এর সাথে টেস্ট করো টাইমআউট এরর আসে কিনা।
  3. downloadSelectedBooks-এর মতো একটা ফাংশন বানাও যেখানে ইচ্ছাকৃতভাবে একটা আইডি "ভুল" রাখো, তারপর succeeded/failed আলাদা করে দেখাও।
  4. Promise.all আর Promise.allSettled দুটোই একই ৩টা Promise (একটা ইচ্ছাকৃতভাবে reject হওয়া) দিয়ে টেস্ট করো, দেখো আচরণে পার্থক্য কী।

পরের পর্বে যা থাকছে

পরের পর্বে আমরা শিখব OOP — Class ও Constructor — JavaScript-এ অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিং কীভাবে ক্লাস দিয়ে সংগঠিত করা যায়, তার বেসিক থেকে শুরু করব।

একটি মন্তব্য পোস্ট করুন

নবীনতর পূর্বতন

যোগাযোগ ফর্ম