সরাসরি পর্বে যান
পর্ব ৩৩ · ৫৯-এর মধ্যে · মডিউল ৫

Promise আর combinator — all, allSettled, race, any

JavaScript Promises — States, the Executor, then/catch/finally, Chaining Rules, Error Propagation, Microtasks, Unhandled Rejections and the Combinators all, allSettled, race, any

Taught in Bangla, this lesson builds a solid model of promises: states and settlement, the executor and Promise.resolve, then, catch and finally, chaining rules for returns and throws, how errors propagate and recover, why handlers always run as microtasks, unhandled rejections, the combinators all, allSettled, race, any and withResolvers, and common anti-patterns.

এই পর্বে যা শিখবেন

  • Promise কোন সমস্যার সমাধান, আর pending/fulfilled/rejected, settled ও resolved শব্দগুলোর সূক্ষ্ম পার্থক্য
  • new Promise-এর executor কখন চলে, delay(ms) বানানো, Promise.resolve/reject আর thenable
  • then, catch, finally আর chaining-এর নিয়ম — error কোন পথে নামে, কীভাবে আবার সামলে ওঠে
  • Promise.all, allSettled, race, any, withResolvers — কোন বাস্তব কাজে কোনটি
  • Callback থেকে promise, একটার পর একটা বনাম একসাথে, চেনা anti-pattern আর AI-এর লেখা promise-কোড যাচাই

১ · Promise কী — ভবিষ্যতের মানের রসিদ

পুরান ঢাকার একটি বিরিয়ানির দোকানে দুপুর একটায় ভিড়। আপনি টাকা দিলেন, কাউন্টার থেকে একটি টোকেন পেলেন — “৪৭ নম্বর”. বিরিয়ানি এখনো হাতে আসেনি, কিন্তু আপনার হাতে এমন একটি জিনিস আছে যা ভবিষ্যতের বিরিয়ানির প্রতিনিধি. টোকেন হাতে আপনি বসে থাকতে বাধ্য নন — পানি কিনতে পারেন, বন্ধুকে ফোন করতে পারেন। পরে দুটোর একটা ঘটবে: ডাক আসবে “৪৭ নম্বর, আপনার প্লেট রেডি”, অথবা “দুঃখিত, বিরিয়ানি শেষ — টাকা ফেরত নিন”. আর একবার যেটা ঘটল, সেটাই চূড়ান্ত। প্লেট হাতে দিয়ে পরে “আসলে শেষ হয়ে গেছে” বলা যায় না।

JavaScript-এর Promise ঠিক এই টোকেন। পর্ব ৩২-এ দেখেছেন, async কাজের ফল পেতে আমরা একটি callback function ভেতরে পাঠাতাম — “কাজ শেষে এটাকে ডাকবেন”. Promise ধারণাটা উল্টে দেয়: async function কোনো callback নেয় না, বরং সাথে সাথে একটি object ফেরত দেয়, যা বলে “ফল এখনো আসেনি, কিন্তু এলে এখানে পাবেন।” আপনি সেই object-এ নিজের handler জুড়ে দেন।

মূল ধারণা

Promise হলো একটি object যা এমন একটি মানের প্রতিনিধি, যা পরে পাওয়া যাবে. শুরুতে সেটি pending. তারপর ঠিক একবার settled হয়: হয় একটি মান (value) নিয়ে fulfilled, নয়তো একটি কারণ (reason, সাধারণত Error object) নিয়ে rejected. ফল নিতে .then() লাগান — আর প্রতিটি .then() একটি নতুন promise ফেরত দেয়। এই একটি নকশা থেকেই আসে চ্যাপ্টা শিকল আর এক জায়গায় সব ভুল ধরার সুবিধা।

Callback থেকে promise — নিয়ন্ত্রণ কার হাতে

পার্থক্যটা শুধু লেখার ধরনে নয়, দায়িত্বে। Callback দিলে আপনি নিজের কোড অন্যের হাতে তুলে দেন — library ঠিক করে কবে, কতবার, কোন argument দিয়ে ডাকবে। ভুল library দুবার ডাকতে পারে, কখনো না-ও ডাকতে পারে, error-কে প্রথম argument-এ দিতে ভুলে যেতে পারে। একে বলে inversion of control. Promise-এ নিয়ন্ত্রণ ফেরত আসে আপনার কাছে, আর ভাষা নিজে কয়েকটি নিশ্চয়তা দেয়:

  • একবারই settle: resolve দুবার ডাকলে, বা resolve-এর পরে reject ডাকলে, পরেরগুলো নিঃশব্দে উপেক্ষিত।
  • দেরিতে জুড়লেও ফল পাবেন: promise শেষ হওয়ার এক ঘণ্টা পরে .then লাগালেও handler ঠিক সেই মান পায়। টোকেন হারিয়ে যায় না।
  • যতজন খুশি শুনতে পারে: একই promise-এ পাঁচটি .then লাগালে পাঁচটিই চলবে, লাগানোর ক্রমে।
  • Handler কখনো সাথে সাথে চলে না: সবসময় চলমান কোড শেষ হওয়ার পরে (অংশ ৫)। তাই আচরণ অনুমানযোগ্য।

তিন অবস্থা, আর দুটি গোলমেলে শব্দ

Promise-এর জীবন

একমুখী রাস্তা — ফেরার পথ নেই

  1. pendingnew Promise(…)ফল আসেনি
  2. fulfilledresolve(value)মান নিয়ে শেষ
  3. rejectedreject(reason)কারণ নিয়ে শেষ
ছবি ৩৩.১ — pending থেকে fulfilled অথবা rejected, যেকোনো একটিতে যায় — আর সেখানেই থেকে যায়। fulfilled আর rejected মিলে বলা হয় settled.

দুটি শব্দ প্রায়ই গুলিয়ে যায়। Settled মানে “আর pending নয়” — fulfilled বা rejected. আর resolved মানে “ভাগ্য ঠিক হয়ে গেছে” — এটা fulfilled-এর সমার্থক নয়. ধরুন আপনি একটি promise p-কে resolve করলেন আরেকটি promise slow দিয়ে: resolve(slow). এখন p resolved — তাকে আর অন্য কিছু দিয়ে resolve বা reject করা যাবে না — কিন্তু সে এখনো pending, কারণ সে slow-এর সাথে বাঁধা পড়েছে (lock in). slow সফল হলে p-ও সফল, slow ব্যর্থ হলে p-ও ব্যর্থ। অর্থাৎ “resolve” ডাকলেও promise rejected হতে পারে! Promise-এর নকশাকারীদের ব্যাখ্যায় (“States and Fates”) এই দ্বিতীয় দিকটির নাম “fate” — resolved বা unresolved.

নিচের ডায়াগ্রামে একটি promise-এর পুরো জীবন দেখুন — ১০০ ms পরে সফল হয়, আর ২০০ ms-এ ব্যর্থ করার চেষ্টাও আছে:

1const p = new Promise((resolve, reject) => {2  setTimeout(() => resolve("৳৫০০ জমা হলো"), 100);3  setTimeout(() => reject(new Error("দেরি")), 200);4});5const p2 = p.then((v) => console.log("পেলাম:", v));6console.log("অপেক্ষা করছি…");
এই মুহূর্তে
p-এর অবস্থা
p-এর মান
অপেক্ষমাণ handler
microtask লাইন
p2
  1. Promise তৈরি, executor চলল। new Promise(…)-এর ভেতরের function (executor) সাথে সাথে চলে, দুটি timer বসায় আর ফিরে আসে। p এখন pending — টোকেন হাতে, প্লেট আসেনি।
  2. Handler জোড়া হলো। p.then(…) handler-টিকে p-এর ভেতরের তালিকায় রাখে আর সাথে সাথে একটি নতুন promise p2 ফেরত দেয়। Handler এখনো চলেনি — চলার কোনো কারণ নেই।
  3. Synchronous কোড শেষ। লাইন ৬ ছাপে “অপেক্ষা করছি…”. Call stack খালি। এখন event loop (পর্ব ৩১) অপেক্ষা করছে timer-এর জন্য; কোনো thread আটকে নেই।
  4. ১০০ ms: resolve. প্রথম timer-এর callback resolve("৳৫০০ জমা হলো") ডাকল। p fulfilled, মান স্থির। অপেক্ষমাণ handler সরাসরি চলে না — তাকে microtask লাইনে পাঠানো হয়।
  5. Microtask চলল। Timer-এর callback শেষ হতেই engine microtask লাইন খালি করে: handler v-তে মান পায় আর ছাপে। Handler কিছু return করেনি, তাই p2 fulfilled হলো undefined দিয়ে — এটা মনে রাখুন, অংশ ৪-এ কাজে লাগবে।
  6. ২০০ ms: reject — কিছুই হলো না। দ্বিতীয় timer reject(…) ডাকল, কিন্তু p আগেই settled. ডাকটি নিঃশব্দে উপেক্ষিত — কোনো error-ও না। এই “একবারই” নিশ্চয়তাই callback-এর “দুবার ডাকা” বাগ থেকে বাঁচায়।

২ · Promise তৈরি — executor, delay আর Promise.resolve

বেশিরভাগ সময় আপনি promise বানাবেন না, পাবেন — fetch (পর্ব ৩৫), Node.js-এর fs/promises, ডেটাবেস driver সবাই promise ফেরত দেয়। নিজে বানাতে হয় মূলত তখন, যখন পুরোনো callback-ধাঁচের কোনো API-কে promise-এ মুড়তে চান। তার যন্ত্র হলো constructor:

function delay(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);   // ms পরে value দিয়ে সফল
  });
}

delay(500, "চা রেডি").then((msg) => console.log(msg));   // আধা সেকেন্ড পরে: চা রেডি

new Promise-এ যে function দেন, তার নাম executor. সে দুটি function হাতে পায় — resolve আর reject. কাজ সফল হলে resolve(মান), ব্যর্থ হলে reject(new Error(…)). Executor-এর ভেতরে কোনো error ছোড়া হলে (throw) সেটিও স্বয়ংক্রিয়ভাবে rejection হয়ে যায়। কিন্তু একটি জিনিস প্রায় সবাইকে অবাক করে: executor সাথে সাথে, synchronous-ভাবে চলে — promise “পরে” কিছু করবে বলে executor-ও পরে চলবে, তা নয়। নিচে লেখা অক্ষরের ক্রম মিলিয়ে দেখুন:

1console.log("ক. অর্ডার নিলাম");2const p = new Promise((resolve) => {3  console.log("খ. executor সাথে সাথে চলে");4  resolve("ঙ. then — সবার শেষে");5  console.log("গ. resolve-এর পরেও কোড চলে");6});7p.then((msg) => console.log(msg));8console.log("ঘ. শেষ লাইন");
এই মুহূর্তে
p
microtask লাইন
call stack
  1. সাধারণ synchronous লাইন। “ক” ছাপা হলো। এখনো কোনো promise নেই।
  2. new Promise — executor তখনই চলে। Constructor executor-কে সাথে সাথে ডাকে, একই call stack-এ। তাই “খ” ছাপা হয় “ঘ”-এর অনেক আগে। Executor-এ ভারী হিসাব (বড় লুপ) রাখলে পাতা ঠিক ততক্ষণই আটকে থাকবে — promise কাজকে আলাদা thread-এ পাঠায় না।
  3. resolve ডাকল, কিন্তু function থামল না। resolve return নয় — এটি শুধু promise-এর অবস্থা ঠিক করে। পরের লাইন চলে, “গ” ছাপা হয়। Guard হিসেবে লিখতে চাইলে return resolve(x) লেখার অভ্যাস কাজে দেয়।
  4. then জোড়া — promise আগেই fulfilled. তবুও handler সাথে সাথে চলে না; সরাসরি microtask লাইনে যায়। Script চলতে থাকে, “ঘ” ছাপা হয়।
  5. Script শেষ, তারপর microtask. Call stack খালি হতেই microtask চলে — “ঙ”. ক্রম: ক, খ, গ, ঘ, ঙ. মনে রাখুন: executor এখনই, handler পরে — এমনকি অপেক্ষা করার কিছু না থাকলেও।

Promise.resolve, Promise.reject আর thenable

যদি মান হাতেই থাকে, কিন্তু কোনো API promise চায়? Promise.resolve(42) সাথে সাথে fulfilled একটি promise দেয়, Promise.reject(new Error("না")) দেয় rejected promise. একটি কাজের নিয়ম: Promise.resolve(x)-এ x যদি আগে থেকেই একটি native promise হয়, নতুন কিছু বানায় না — সেটাকেই ফেরত দেয়। তাই “মান হোক বা promise, আমি promise চাই” — এমন জায়গায় Promise.resolve(x) নিরাপদ মোড়ক।

আর যদি x এমন কোনো object হয় যার একটি then method আছে? JavaScript তখন সেটাকে promise-এর মতো ধরে নেয় — একে বলে thenable. Promise.resolve({ then(ok) { ok("৪২"); } }) fulfilled হয় "৪২" দিয়ে। এই নিয়মের জন্যই jQuery, Bluebird বা পুরোনো কোনো library-র promise আর native promise একই শিকলে মেশানো যায় (ভাবনার প্রশ্ন ২)। উল্টো দিকটা হলো, ভুল করে কোনো সাধারণ object-এ then নামের property রাখলে, সেটি promise থেকে return করলে অদ্ভুত আচরণ পাবেন — শিকল সেই then ডেকে বসে থাকবে।

৩ · ফল নেওয়া — then, catch আর finally

Promise-এর ফল নেওয়ার তিনটি method — প্রত্যেকটি একটি নতুন promise ফেরত দেয়:

methodকখন চলেনতুন promise কী নিয়ে শেষ হয়
.then(onFulfilled, onRejected)সফল হলে প্রথমটি, ব্যর্থ হলে দ্বিতীয়টি (দুটোই ঐচ্ছিক)যে handler চলল তার ফেরত মান; handler throw করলে সেই error দিয়ে rejected
.catch(onRejected)শুধু ব্যর্থ হলে; আসলে .then(undefined, onRejected)-এর সংক্ষেপhandler-এর ফেরত মান দিয়ে fulfilled — অর্থাৎ শিকল আবার সামলে ওঠে
.finally(onFinally)সফল বা ব্যর্থ — দুই ক্ষেত্রেই (ES2018)আগের ফলই হুবহু এগিয়ে দেয়; handler কোনো argument পায় না, আর তার ফেরত মান উপেক্ষিত (তবে সে throw করলে বা rejected promise ফেরত দিলে সেটাই নতুন ফল)

finally-এর “এগিয়ে দেওয়া” আচরণটি গুরুত্বপূর্ণ: Promise.resolve(5).finally(() => 99).then(console.log) ছাপে 5, 99 নয়। এটা পর্ব ২৯-এর finally block-এর মতোই — পরিষ্কারের কাজের জায়গা (লোডিং চিহ্ন সরানো, বাটন আবার চালু), ফল বদলানোর নয়।

.then(ok, fail) আর .then(ok).catch(fail) এক নয়। প্রথমটিতে ok-এর ভেতরে error হলে fail তাকে ধরে না — কারণ দুটোই একই promise-এর দুই পথ। দ্বিতীয়টিতে catch লাগানো হয়েছে then-এর ফেরত promise-এ, তাই সে আগের promise-এর ব্যর্থতা আর ok-এর ভেতরের error — দুটোই ধরে। সাধারণ নিয়ম: শিকলের শেষে একটি .catch.

৪ · Chaining-এর নিয়ম — return, throw আর error-এর পথ

Promise-এর আসল শক্তি chaining-এ। যেহেতু প্রতিটি .then নতুন promise দেয়, আপনি লিখতে পারেন a().then(b).then(c).then(d) — পর্ব ৩২-এর ডানদিকে সরে যাওয়া callback-এর পিরামিডের বদলে একটি সোজা লাইন। নতুন promise-টি কী নিয়ে শেষ হবে, তা ঠিক হয় handler কী করল তা দেখে:

১. মান return

.then((n) => n * 2) — নতুন promise সেই মান দিয়ে fulfilled; পরের then সেটা পায়।

সাধারণ রূপান্তর

২. promise return

.then((id) => reserve(id)) — শিকল অপেক্ষা করে সেই promise শেষ হওয়া পর্যন্ত, তারপর তার ফলই এগিয়ে যায়।

পরের async ধাপ

৩. throw

.then(() => { throw new Error("স্টক নেই"); }) — নতুন promise rejected; পরের সব then বাদ, প্রথম catch-এ নামে।

ব্যর্থতা জানানো

৪. return ভুলে যাওয়া

.then((id) => { reserve(id); }) — কিছু return হয়নি, তাই undefined দিয়ে সাথে সাথে fulfilled. শিকল অপেক্ষা করে না, reserve-এর ব্যর্থতাও কেউ ধরে না।

সবচেয়ে চেনা বাগ

চতুর্থটি নিয়ে আরেকটু। Arrow function-এ { } দিলে implicit return থাকে না (পর্ব ১৩) — (id) => reserve(id) মান ফেরত দেয়, কিন্তু (id) => { reserve(id); } দেয় না। ফল দুই রকম ক্ষতি: পরের ধাপ ভুল মান (undefined) পায় আর reserve শেষ হওয়ার আগেই চলে যায়; আর reserve ব্যর্থ হলে তার rejection এই শিকলের বাইরে একা পড়ে থাকে — unhandled. কোনো error message দেখায় না যে “return নেই” — তাই এটা খুঁজে পাওয়া কঠিন। নিয়ম মুখস্থ রাখুন: then-এর ভেতরে async কিছু শুরু করলে সেটা return করুন।

Error কোন পথে নামে, আর কীভাবে সামলে ওঠে

পর্ব ২৯-এ দেখেছেন, synchronous কোডে throw হলে error call stack বেয়ে প্রথম try/catch পর্যন্ত ওঠে। Promise শিকলে তার প্রতিরূপ: rejection শিকল বেয়ে নিচে নামে, যতক্ষণ না প্রথম onRejected handler (সাধারণত .catch) পায়। মাঝের সব then(onFulfilled) বাদ পড়ে। আর catch যদি একটি মান return করে, শিকল আবার fulfilled পথে ফিরে আসে:

1Promise.resolve(2)2  .then((n) => n * 10)3  .then((n) => { throw new Error("সীমা পার: " + n); })4  .then((n) => console.log("চলবে না", n))5  .catch((e) => { console.log("ধরা:", e.message); return 0; })6  .then((n) => console.log("আবার চালু:", n))7  .finally(() => console.log("শেষ"));
এই ধাপের promise
অবস্থা
মান / কারণ
handler
  1. শুরু: fulfilled 2. Promise.resolve(2) সাথে সাথে সফল একটি promise. প্রতিটি পরের লাইন আলাদা একটি নতুন promise তৈরি করে — মোট সাতটি promise-এর শিকল।
  2. মান return → পরের ধাপ পায়। n * 10 = 20, তাই এই ধাপের promise fulfilled 20. নিয়ম ১।
  3. throw → rejected. Handler-এর ভেতরে throw হলো। এখানে কোনো try/catch লাগেনি — then নিজেই error ধরে নতুন promise-কে rejected করে, কারণ সেই Error object.
  4. পরের then বাদ পড়ল। লাইন ৪-এ শুধু onFulfilled আছে, onRejected নেই। তাই handler চলে না, rejection হুবহু পরের promise-এ চলে যায় — “চলবে না” কখনো ছাপা হয় না। দশটি then থাকলেও দশটিই এভাবে পার হতো।
  5. catch ধরল আর মান ফেরত দিল। catch error পেল, ছাপল, তারপর return 0. মান return মানে নিয়ম ১ — নতুন promise fulfilled 0. ব্যর্থতা “সামলানো” হয়ে গেছে, শিকল আবার সুস্থ। (catch-এর ভেতরে আবার throw করলে শিকল rejected থাকত — পর্ব ২৯-এর rethrow-এর মতো।)
  6. শিকল আবার চলল, finally শেষে। লাইন ৬ পায় 0 — “আবার চালু: 0”. তারপর finally “শেষ” ছাপে। এই ধাঁচটাই বাস্তবের ডিফল্ট মান: “রিভিউ লোড না হলে খালি তালিকা দেখান, কিন্তু পাতা চলুক।”

বাস্তব শিকল — অর্ডার দেওয়া

একটি অনলাইন দোকানে অর্ডারের চারটি ধাপ: স্টক যাচাই → মাল সংরক্ষণ (reserve) → টাকা কাটা → SMS পাঠানো। প্রতিটি ধাপ async, আর যেকোনো ধাপ ব্যর্থ হতে পারে। Callback দিয়ে এটা চার স্তর গভীর পিরামিড, প্রতিটি স্তরে আলাদা error-পরীক্ষা। Promise দিয়ে একটি লাইন আর একটি catch. নেটওয়ার্কের বদলে এখানে setTimeout-ভিত্তিক নকল async function:

const delay = (ms, value) => new Promise((resolve) => setTimeout(resolve, ms, value));
const stock = { "ফ্যান": 0, "কেটলি": 4 };

function checkStock(item) {
  return delay(50).then(() => {
    if (!stock[item]) throw new Error(`${item} স্টকে নেই`);
    return item;
  });
}
const reserve = (item) => delay(50, `${item} রিজার্ভ`);
const charge = (r) => delay(50, `${r}, ৳১২০০ কাটা হলো`);
const sendSms = (msg) => { console.log("SMS:", msg); return "অর্ডার সম্পন্ন"; };

function placeOrder(item) {
  console.log("অর্ডার শুরু:", item);
  return checkStock(item).then(reserve).then(charge).then(sendSms)
    .catch((err) => `ব্যর্থ: ${err.message}`)
    .finally(() => console.log("লোডিং চিহ্ন সরানো"));
}
placeOrder("কেটলি")
  .then((r) => { console.log(r); return placeOrder("ফ্যান"); })
  .then((r) => console.log(r));
console.log("এই লাইন আগে — কেউ অপেক্ষায় বসে নেই");

আউটপুটের প্রথম দুই লাইন: অর্ডার শুরু: কেটলি, তারপর সাথে সাথে এই লাইন আগে — কেউ অপেক্ষায় বসে নেই — placeOrder শুধু শিকল বসিয়ে ফিরে এসেছে, ফলের জন্য script থামেনি। ১৫০ ms পরে SMS: কেটলি রিজার্ভ, ৳১২০০ কাটা হলো, তারপর লোডিং চিহ্ন সরানো আর অর্ডার সম্পন্ন. দ্বিতীয় অর্ডারে (অর্ডার শুরু: ফ্যান) checkStock-এর ভেতরে throw হয়, তাই reserve, charge, sendSms — তিনটিই বাদ; সরাসরি catch → লোডিং চিহ্ন সরানো → ব্যর্থ: ফ্যান স্টকে নেই. লক্ষ করুন: .then(reserve)-এ function-টি সরাসরি দেওয়া — .then((x) => reserve(x))-এর সমান, তাই return ভুলে যাওয়ার সুযোগই নেই। আর প্রথম অর্ডারের পরে return placeOrder("ফ্যান") — return থাকায় দ্বিতীয় অর্ডার প্রথমটির পরেই শুরু হয়েছে। কোনো ধাপের ভুল যেন টাকা কেটে ফেলার পরে না আসে — সেজন্য বাস্তবে যাচাই আগে, টাকা কাটা প্রায় শেষে রাখা হয়।

৫ · Handler সবসময় পরে চলে — microtask আর unhandled rejection

Promise-এর handler কখনো “এখনই” চলে না — promise আগে থেকেই fulfilled হলেও না। মানদণ্ড বলে, handler একটি job হিসেবে লাইনে দাঁড়াবে; ব্রাউজার আর Node.js সেটাকে microtask queue-তে রাখে। পর্ব ৩১-এর নিয়মটা মনে করুন: বর্তমান কোড শেষ হলেই সব microtask চলে — পরের timer বা click-এর মতো task-এর আগে. তাই নিচের কোডে ক্রম হয় ১, ৩, ২:

setTimeout(() => console.log("২. timer (task)"), 0);
Promise.resolve().then(() => console.log("৩? না — এটা আগে: microtask"));
console.log("১. synchronous");
// প্রত্যাশিত আউটপুট:
// ১. synchronous
// ৩? না — এটা আগে: microtask
// ২. timer (task)

কেন এই নিয়ম? কারণ “কখনো সাথে সাথে, কখনো পরে” চলা API সবচেয়ে বিপজ্জনক — একই কোড ক্যাশে ডেটা থাকলে এক ক্রমে, না থাকলে আরেক ক্রমে চলে, আর বাগ শুধু মাঝে মাঝে দেখা দেয়। Node.js-জগতে একে বলা হয় “releasing Zalgo”. Promise এই অনিশ্চয়তা পুরোপুরি মুছে দেয়: handler সবসময় async. উল্টো দিকে একটি সতর্কতা: microtask-এর ভেতরে অবিরাম নতুন microtask বানালে (যেমন নিজেকে বারবার ডাকা then) event loop কখনো timer বা রেন্ডারিং-এ পৌঁছায় না — পাতা জমে যায়।

কেউ না ধরা rejection

Synchronous error কেউ না ধরলে uncaught হয় (পর্ব ২৯)। Promise-এর ক্ষেত্রে সমস্যাটা সূক্ষ্ম: rejected promise-এ হয়তো পরে কেউ .catch লাগাবে — engine কীভাবে জানবে? তাই নিয়ম হলো: একটি promise rejected হলো, আর microtask লাইন খালি হওয়া পর্যন্ত তাতে কোনো handler লাগল না — তখন সেটা unhandled rejection.

  • ব্রাউজারে: console-এ লাল লেখা Uncaught (in promise) Error: …, আর window-এ unhandledrejection event. পাতা চলতে থাকে, কিন্তু কাজটি নিঃশব্দে ব্যর্থ। পরে কেউ handler লাগালে rejectionhandled event আসে। পর্ব ২৯-এর global handler এই event-টিই শোনে — রিপোর্ট করার জন্য।
  • Node.js-এ: Node.js 15 থেকে ডিফল্ট মোড --unhandled-rejections=throw: rejection-টি uncaught exception হিসেবে ছোড়া হয়, আর কোনো process.on("unhandledRejection") বা uncaughtException handler না থাকলে পুরো process বন্ধ হয়ে যায় (exit code 1). এর আগে Node শুধু একটি সতর্কবার্তা ছাপত — অনেক সার্ভার বছরের পর বছর লুকানো ব্যর্থতা নিয়ে চলত।

Unhandled rejection-এর ভয়ে সবখানে .catch(() => {}) বসাবেন না — এটা পর্ব ২৯-এর খালি catch {}-এর promise-রূপ। error উধাও, কাজ ব্যর্থ, কেউ জানে না। শিকলের শেষে এমন catch রাখুন যা সত্যিই কিছু করে: ব্যবহারকারীকে বার্তা, ডিফল্ট মান, বা log-এ রিপোর্ট। আর একটি “fire-and-forget” কাজ (যেমন analytics পাঠানো) সত্যিই ব্যর্থ হতে দিতে চাইলে অন্তত log করুন।

৬ · Combinator — all, allSettled, race, any

এ পর্যন্ত একটির পর একটি কাজ দেখেছি। কিন্তু প্রায়ই কয়েকটি কাজ একসাথে চালাতে চান, তারপর একটি সিদ্ধান্ত নিতে চান। এর জন্য চারটি combinator — প্রত্যেকটি একগুচ্ছ promise (আসলে যেকোনো iterable) নিয়ে একটি নতুন promise দেয়। পার্থক্য শুধু দুটি প্রশ্নে: কখন শেষ হবে, আর কী ফল দেবে।

combinatorসফল হয় যখন…ব্যর্থ হয় যখন…কবে থেকে
Promise.allসবগুলো সফল → মানের array, মূল ক্রমেযেকোনো একটি ব্যর্থ হওয়ামাত্র (fail-fast), সেই কারণ দিয়েES2015
Promise.allSettledসবগুলো settled → প্রতিটির { status, value } বা { status, reason }কখনো নাES2020
Promise.raceপ্রথম settled-টি সফল হলেপ্রথম settled-টি ব্যর্থ হলেES2015
Promise.anyপ্রথম সফল-টির মান নিয়েসবগুলো ব্যর্থ হলে — AggregateError, কারণগুলো errors array-তেES2021

একই তিনটি promise চারটি combinator-কে দিলে কী হয়, সময় ধরে দেখুন। A দ্রুত কিন্তু ব্যর্থ (৫০ ms), B ধীর আর সফল (১৫০ ms), C মাঝারি আর সফল (১০০ ms). ধরুন wait(ms, মান, ব্যর্থ?) একটি নকল async function:

1const A = wait(50, "A বন্ধ", true);  // ৫০ ms-এ ব্যর্থ2const B = wait(150, "B");           // ১৫০ ms-এ সফল3const C = wait(100, "C");           // ১০০ ms-এ সফল4Promise.all([A, B, C]);5Promise.allSettled([A, B, C]);6Promise.race([A, B, C]);7Promise.any([A, B, C]);
সময় অনুযায়ী অবস্থা
সময়
all
allSettled
race
any
  1. ০ ms: সবাই শুরু। তিনটি কাজ একসাথে চলছে — combinator কাজ শুরু করে না, কাজ তো promise তৈরির মুহূর্তেই শুরু হয়ে গেছে (executor সাথে সাথে চলে)। Combinator শুধু ফলের দিকে তাকিয়ে থাকে। চারটি combinator-ই এখন pending.
  2. ৫০ ms: A ব্যর্থ। all সাথে সাথে rejected — “সবগুলো চাই” শর্ত আর কখনো পূরণ হবে না, অপেক্ষা অর্থহীন। race-ও rejected — A-ই প্রথম settled, আর race দেখে না ফলটা ভালো না খারাপ। any A-কে উপেক্ষা করে অপেক্ষায়; allSettled A-র ফল লিখে রাখে।
  3. ১০০ ms: C সফল। any এর অপেক্ষাতেই ছিল — প্রথম সফল, তাই fulfilled "C". লক্ষ করুন, B তালিকায় C-র আগে থাকলেও any ক্রম নয়, সময় দেখে। allSettled এখন ৩টির ২টি জানে।
  4. ১৫০ ms: B সফল। এবার allSettled শেষ: [{ status: "rejected", reason: Error }, { status: "fulfilled", value: "B" }, { status: "fulfilled", value: "C" }] — ক্রম মূল তালিকার, শেষ হওয়ার নয়। all সফল হলেও ফলের ক্রম একই নিয়মে মূল তালিকার হতো।
  5. সারাংশ — আর একটি সত্য। all ৫০ ms-এ হাল ছেড়ে দিলেও B আর C চলতেই থাকল, শেষও হলো — promise বাতিল করা যায় না, combinator শুধু তাদের ফল উপেক্ষা করে (ভাবনার প্রশ্ন ৫)। আর যদি তিনটিই ব্যর্থ হতো, any দিত AggregateError: All promises were rejected, allSettled তবুও সফল হতো তিনটি rejected ফল নিয়ে।

এবার নিজে চালিয়ে দেখুন — প্রতিটি combinator পায় তিনটি নতুন promise (একই সময়সূচি), আর শেষে একটি any যার সবগুলোই ব্যর্থ:

const wait = (ms, v, fail = false) => new Promise((ok, no) =>
  setTimeout(() => (fail ? no(new Error(v)) : ok(v)), ms));
const three = () => [
  wait(50, "A বন্ধ", true),   // দ্রুত, কিন্তু ব্যর্থ
  wait(150, "B"),             // ধীর, সফল
  wait(100, "C"),             // মাঝারি, সফল
];
const show = (name) => [
  (v) => console.log(name, "→ সফল:", v),
  (e) => console.log(name, "→ ব্যর্থ:", e.message),
];
Promise.all(three()).then(...show("all"));
Promise.race(three()).then(...show("race"));
Promise.any(three()).then(...show("any"));
Promise.allSettled(three()).then((results) =>
  console.log("allSettled →", results.map((r) =>
    r.status === "fulfilled" ? r.value : `ব্যর্থ (${r.reason.message})`)));
Promise.any([wait(60, "x", true), wait(70, "y", true)])
  .catch((e) => console.log(e.name, e.message, e.errors.map((x) => x.message)));

আউটপুট সময়ের ক্রমে আসে: ৫০ ms-এ all → ব্যর্থ: A বন্ধ আর race → ব্যর্থ: A বন্ধ; ৭০ ms-এ AggregateError All promises were rejected [ 'x', 'y' ] — দুটিই ব্যর্থ, তাই any-র কারণ একটি AggregateError, যার errors-এ দুটি আসল Error (বার্তাটি V8-এর; Firefox-এর লেখা ভিন্ন); ১০০ ms-এ any → সফল: C; আর ১৫০ ms-এ allSettled → [ 'ব্যর্থ (A বন্ধ)', 'B', 'C' ] — মূল তালিকার ক্রমে। .then(...show("all"))-এ spread দিয়ে দুটি handler একসাথে দেওয়া — then(onFulfilled, onRejected). A প্রতিটি combinator-এর হাতে ধরা পড়েছে বলে কোনো unhandled rejection হয়নি। allSettled-এর পুরো object ছাপার বদলে map করা হয়েছে, কারণ Error object ছাপলে পুরো stack trace আসে।

কোন কাজে কোনটি — চারটি বাস্তব দৃশ্য

পণ্যের পাতা — Promise.all. একটি পণ্যের পাতায় লাগে পণ্যের তথ্য, রিভিউ আর ডেলিভারির আনুমানিক সময়। তিনটি একে অপরের ওপর নির্ভর করে না — তাই একসাথে চান:

Promise.all([getProduct(id), getReviews(id), getDeliveryEstimate(id, "সিলেট")])
  .then(([product, reviews, eta]) => render(product, reviews, eta))   // ক্রম নিশ্চিত
  .catch((err) => showError("পাতা লোড হয়নি", err));

তিনটি কাজের প্রতিটি ৩০০ ms নিলে মোট সময় প্রায় ৩০০ ms, একটির পর একটি করলে ৯০০ ms. Array destructuring (পর্ব ১৭) দিয়ে ফল নেওয়া যায় কারণ ক্রম তালিকার, শেষ হওয়ার নয়। কিন্তু ভাবুন: রিভিউ সার্ভার পড়ে গেলে পুরো পাতাই “লোড হয়নি”? এখানেই পরের combinator.

Dashboard — Promise.allSettled. একটি দোকানের মালিকের dashboard-এ ছয়টি widget: আজকের বিক্রি, স্টক, অর্ডার, bKash লেনদেন, রিভিউ, আবহাওয়া। আবহাওয়ার API বন্ধ বলে বাকি পাঁচটি দেখাবে না — এটা অগ্রহণযোগ্য। allSettled সবার ফল দেয়, প্রত্যেকে নিজের status নিয়ে: যেগুলো "fulfilled" সেগুলো দেখান, "rejected"-গুলোর জায়গায় “এই তথ্য এখন পাওয়া যাচ্ছে না” লিখুন। প্রশ্নটা হলো — একটির ব্যর্থতায় কি পুরো কাজ অর্থহীন? হ্যাঁ হলে all (যেমন অর্ডারের সব আইটেমের দাম না পেলে মোট দাম দেখানো যায় না), না হলে allSettled.

Request timeout — Promise.race. কোনো সার্ভার উত্তর না দিলে promise চিরকাল pending থাকতে পারে। race দিয়ে একটি সময়সীমা বসান:

function withTimeout(promise, ms) {
  let timer;
  const timeout = new Promise((_, reject) => {
    timer = setTimeout(() => reject(new Error(`${ms} ms পার, উত্তর আসেনি`)), ms);
  });
  return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
}

withTimeout(getBalance("01711…"), 3000).then(show).catch(showRetryButton);

finally-তে clearTimeout জরুরি — নইলে কাজ সফল হলেও timer বেঁচে থাকে, আর Node.js-এ process অকারণে খোলা থাকে। আর মনে রাখুন: timeout হলেও আসল কাজটি থামেনি, সার্ভারে request চলছেই — শুধু আপনি ফলটা উপেক্ষা করছেন। সত্যিকারের বাতিল আর AbortSignal.timeout() পর্ব ৩৬-এ।

সবচেয়ে দ্রুত mirror — Promise.any. একটি বড় ফাইল তিনটি সার্ভারে আছে — ঢাকা, সিঙ্গাপুর, ফ্রাঙ্কফুর্ট। যেটা আগে উত্তর দেয় সেটাই চাই, কোনোটা বন্ধ থাকলে কিছু যায় আসে না: Promise.any([ping(dhaka), ping(sg), ping(fra)]). এখানে race ভুল হতো — ঢাকার সার্ভার ১০ ms-এ “connection refused” দিলে race সেটাকেই ফল ধরে ব্যর্থ হতো, যদিও সিঙ্গাপুর ৮০ ms-এ ঠিকই দিত। আর সবগুলো ব্যর্থ হলে err.errors-এ তিনটি কারণই পাবেন — log-এর জন্য দারুণ।

Promise.withResolvers — executor-এর বাইরে থেকে শেষ করা

কখনো promise তৈরি হয় এক জায়গায়, আর শেষ হয় সম্পূর্ণ অন্য জায়গায় — যেমন “ব্যবহারকারী নিশ্চিত করুন” dialog: promise দেওয়া হলো এখন, resolve হবে বাটনে ক্লিক হলে। আগে এর জন্য লেখা হতো let resolve; const p = new Promise((r) => { resolve = r; });. ES2024-এ এসেছে Promise.withResolvers(), যা একসাথে { promise, resolve, reject } দেয়:

function askConfirm(dialog) {
  const { promise, resolve } = Promise.withResolvers();
  dialog.querySelector(".yes").addEventListener("click", () => resolve(true), { once: true });
  dialog.querySelector(".no").addEventListener("click", () => resolve(false), { once: true });
  dialog.showModal();
  return promise;
}
askConfirm(deleteDialog).then((ok) => ok && deleteOrder(id));

Edge আর Chrome 119 থেকে, Firefox 121 থেকে, Safari 17.4 থেকে এটি আছে; Node.js-এ 22 থেকে — তাই Node.js 20-এ চালাতে হলে পুরোনো let resolve কৌশলই লাগবে।

৭ · Callback থেকে promise, একসাথে বনাম পরপর, আর চেনা anti-pattern

Callback-কে promise-এ মোড়ানো

পর্ব ৩২-এর Node.js-ধাঁচের callback — শেষ argument-এ (err, value) — promise-এ রূপান্তর করার কাজকে বলে promisify. হাতে করা যায়, আবার Node.js-এ তৈরি যন্ত্রও আছে:

const fs = require("node:fs");
const { promisify } = require("node:util");

// হাতে মোড়ানো: callback-এর দুই পথ → resolve/reject
function readFileP(path) {
  return new Promise((resolve, reject) => {
    fs.readFile(path, "utf8", (err, text) => (err ? reject(err) : resolve(text)));
  });
}

// util.promisify একই কাজ করে দেয়
const readFile2 = promisify(fs.readFile);

fs.writeFileSync("order.txt", "কেটলি × ১");
readFileP("order.txt").then((t) => console.log("পেলাম:", t));
readFile2("nai.txt", "utf8").catch((e) => console.log("ব্যর্থ:", e.code));
// প্রত্যাশিত আউটপুট (ক্রম ফাইল সিস্টেমের ওপর নির্ভর করতে পারে):
// পেলাম: কেটলি × ১
// ব্যর্থ: ENOENT

বাস্তবে আজ এটা খুব কম লাগে — Node.js-এর বেশিরভাগ API-র promise-সংস্করণ আছে (require("node:fs/promises"), timers/promises-এর setTimeout). তবে পুরোনো library-তে আটকে গেলে এই কৌশলই ভরসা। এখানে new Promise ব্যবহার করা ঠিক, কারণ ভেতরে কোনো promise নেই — শুধু callback.

পরপর বনাম একসাথে

দশটি invoice লোড করবেন। দুটি পথ:

const ids = [11, 12, 13];

// পরপর: একটি শেষ হলে পরেরটি শুরু (মোট ≈ ৩ × এক কাজের সময়)
const sequential = ids.reduce(
  (chain, id) => chain.then((list) => loadInvoice(id).then((inv) => [...list, inv])),
  Promise.resolve([])
);

// একসাথে: সব একবারে শুরু (মোট ≈ সবচেয়ে ধীর কাজটির সময়)
const parallel = Promise.all(ids.map(loadInvoice));

reduce দিয়ে শিকল (পর্ব ১৬): শুরু একটি খালি array-র promise দিয়ে, আর প্রতিটি id শিকলে একটি করে ধাপ যোগ করে। পরপর তখনই দরকার যখন একটি ধাপ আগেরটির ফলের ওপর নির্ভর করে, অথবা ক্রম জরুরি (ব্যাংকে একটির পর একটি লেনদেন)। বাকি সব ক্ষেত্রে একসাথে অনেক দ্রুত। পরের পর্বের async/await-এ পরপর-এর কোডটা একটি সাধারণ for…of লুপ হয়ে যাবে — অনেক বেশি পড়ার মতো।

তবে “সব একসাথে” সীমাহীন নয়। ৫,০০০টি ছবি আপলোড করতে Promise.all(photos.map(upload)) লিখলে ৫,০০০টি request একসাথে ছুটবে — ব্রাউজার আটকে যাবে, সার্ভার rate limit-এ আপনাকে আটকাবে। দরকার concurrency limit: একসাথে সর্বোচ্চ ৪টি, একটি শেষ হলে পরেরটি। এর নকশা পর্ব ৩৬-এ।

চারটি চেনা anti-pattern

// ✗ ১. nested then — callback hell ফিরে এলো, আর return নেই
getUser(id).then((user) => {
  getOrders(user).then((orders) => show(orders));   // এর error কে ধরবে?
});
// ✓ চ্যাপ্টা শিকল, শেষে একটি catch
getUser(id).then(getOrders).then(show).catch(report);

// ✗ ২. explicit construction — আগেই থাকা promise-কে আবার new Promise-এ মোড়ানো
function getPrice(id) {
  return new Promise((resolve, reject) => {
    fetchProduct(id).then((p) => resolve(p.price));   // reject-এর পথ ভুলে গেছে!
  });
}
// ✓ promise তো আছেই — শুধু রূপান্তর করে return
const getPrice2 = (id) => fetchProduct(id).then((p) => p.price);
  1. Nested then: then-এর ভেতরে then লিখলে পর্ব ৩২-এর পিরামিড ফিরে আসে। ভেতরের promise return না করলে বাইরের শিকল তার জন্য অপেক্ষা করে না, আর ভেতরের ব্যর্থতা বাইরের catch-এ পৌঁছায় না। ব্যতিক্রম শুধু তখন, যখন ভেতরে আগের ধাপের দুটি মান একসাথে লাগে — সেটাও async/await-এ (পর্ব ৩৪) সহজে মেটে।
  2. Explicit construction anti-pattern: উপরের getPrice-এ fetchProduct ব্যর্থ হলে বাইরের promise চিরকাল pending — কোনো error নেই, কোনো উত্তর নেই, শুধু ঘুরতে থাকা লোডিং চিহ্ন। নিয়ম: new Promise শুধু callback বা event-ভিত্তিক API মোড়াতে।
  3. catch ভুলে যাওয়া: শিকলের শেষে catch নেই মানে প্রতিটি ব্যর্থতা unhandled rejection — ব্রাউজারে নীরব ব্যর্থতা, Node.js-এ ক্র্যাশ। যে function শিকল return করে, তার ডাকার জায়গায় catch থাকলেই যথেষ্ট; মাঝের প্রতিটি function-এ catch লাগবে না।
  4. Callback আর promise মেশানো: একই function-এ callback নেওয়া আর promise ফেরত দেওয়া, বা then-এর ভেতরে callback-ধাঁচের API ডেকে ফল বাইরের variable-এ রাখা। এতে দুই জগতের error-পথ আলাদা হয়ে যায় — একটি পথ সবসময় বাদ পড়ে। আগে মুড়ে নিন (promisify), তারপর পুরোটা promise-এ।
C/C++/Python/Java জানলে

ধারণাটা সব ভাষায় আছে, নাম আলাদা। Java-র CompletableFuture-এ thenApply (মান রূপান্তর) আর thenCompose (আরেকটি future) আলাদা method, যেখানে JavaScript-এর একটি then দুটোই করে; allOf/anyOf হলো all/any-র আত্মীয় (Java-র Executor আর CompletableFuture). C++-এর std::promise/std::future জোড়ায় promise হলো লেখার দিক আর future পড়ার দিক — কিন্তু future.get() thread আটকে অপেক্ষা করে, JavaScript-এ এমন আটকানো অপেক্ষা নেই (C++-এর async, future আর atomics). Python asyncio-র Future/Task আর asyncio.gather সবচেয়ে কাছের — তবে Python-এ coroutine await বা Task না বানালে চলেই না, আর JavaScript-এর promise তৈরি হওয়ামাত্র কাজ শুরু (Python-এর asyncio).

ইন্টারভিউর একটি চিরচেনা প্রশ্ন: “Promise.all নিজে লিখুন।” মূল কৌশল তিনটি — ফল রাখুন সূচক ধরে (শেষ হওয়ার ক্রমে নয়), কয়টি বাকি তা গুনুন, আর প্রথম ব্যর্থতায় reject:

function myAll(items) {
  return new Promise((resolve, reject) => {
    const results = [];
    let left = items.length;
    if (left === 0) return resolve(results);       // খালি তালিকা → সাথে সাথে []
    items.forEach((item, i) => {
      Promise.resolve(item).then((value) => {        // সাধারণ মানও চলবে
        results[i] = value;                          // ক্রম ঠিক রাখে সূচক
        if (--left === 0) resolve(results);
      }, reject);                                    // প্রথম ব্যর্থতাই জেতে
    });
  });
}
পরীক্ষা ও ইন্টারভিউতে
  • Promise-এর তিন অবস্থা কী, আর একবার fulfilled হলে কি rejected হতে পারে? — pending, fulfilled, rejected; settled হওয়ার পর আর বদলায় না, পরের resolve/reject উপেক্ষিত।
  • Promise.all আর Promise.allSettled-এর পার্থক্য? — all প্রথম ব্যর্থতায় rejected হয় (fail-fast); allSettled সবার জন্য অপেক্ষা করে প্রতিটির status দেয়, নিজে কখনো rejected হয় না।
  • race আর any-এর পার্থক্য? — race প্রথম settled-টি নেয় (ব্যর্থ হলেও); any প্রথম সফল-টি নেয়, সব ব্যর্থ হলে AggregateError.
  • new Promise-এর executor কখন চলে? — সাথে সাথে, synchronous-ভাবে; শুধু then-এর handler পরে (microtask হিসেবে) চলে।
  • Promise.all নিজে লিখুন। — উপরের myAll: সূচক ধরে ফল রাখা, বাকি গোনা, প্রথম rejection-এ reject; আসল Promise.all যেকোনো iterable নেয়, এই সংস্করণ শুধু array.

৮ · AI-এর যুগে — promise-কোডে AI-এর চেনা ভুল

AI promise-কোড খুব দ্রুত লেখে, আর প্রায়ই সেটা চলেও — কারণ পরীক্ষার সময় নেটওয়ার্ক ভালো, সার্ভার চালু। Promise-এর বাগ দেখা দেয় ব্যর্থতার পথে: একটি সার্ভার ধীর, একটি request ব্যর্থ, দুটি কাজের ক্রম উল্টে গেল। তাই AI-এর লেখা কোড পড়ার সময় “সব ঠিক থাকলে” নয়, “একটি ধাপ ব্যর্থ হলে কী হয়” — এই প্রশ্ন নিয়ে পড়ুন।

AI যা প্রায়ই লেখেসমস্যাযা চাইবেন
.then((x) => { save(x); })return নেই — শিকল অপেক্ষা করে না, save-এর ব্যর্থতা unhandledasync কিছু শুরু করলে return
then-এর ভেতরে then-এর পিরামিডcallback hell ফিরে আসে, ভেতরের error বাইরে পৌঁছায় নাচ্যাপ্টা শিকল, বা async/await (পর্ব ৩৪)
return new Promise((res) => fetchX().then(res))explicit construction — reject-পথ হারায়, promise চিরকাল pendingসরাসরি return fetchX().then(...)
dashboard-এ Promise.allএকটি widget-এর ব্যর্থতায় পুরো পাতা ফাঁকাallSettled, প্রতিটি ফল আলাদা সামলানো
.catch(() => {}) বা .catch(console.log)error গিলে ফেলা; পরের ধাপ undefined পেয়ে অন্য জায়গায় ভাঙেডিফল্ট মান, ব্যবহারকারীর বার্তা বা rethrow
লুপে একটির পর একটি await/then, যেখানে কাজগুলো স্বাধীনঅকারণে ধীরস্বাধীন কাজ Promise.all-এ, তবে সীমাসহ

AI-কে promise-কোড লিখতে বললে নিয়ম আর ব্যর্থতার পরীক্ষা আগেই চেয়ে নিন:

আমি JavaScript-এ একটি অনলাইন দোকানের loadProductPage(id) ফাংশন লিখছি। getProduct(id), getReviews(id) আর getDeliveryEstimate(id) — তিনটিই promise ফেরত দেয়। নিচের নিয়ম মেনে কোড দাও: ১. তিনটি কাজ একসাথে চালাবে। পণ্যের তথ্য না পেলে পুরো পাতা ব্যর্থ; কিন্তু রিভিউ বা ডেলিভারি ব্যর্থ হলে বাকিটা দেখাবে — কোথায় all আর কোথায় allSettled, কারণসহ বলো। ২. প্রতিটি then-এর ভেতরে async কাজ থাকলে অবশ্যই return করবে; nested then লিখবে না। ৩. আগে থেকে promise ফেরত দেয় এমন কাজ new Promise দিয়ে মুড়বে না। ৪. কোথাও .catch(() => {}) বা শুধু console.log করে error গিলবে না; ব্যর্থ অংশের জন্য স্পষ্ট ডিফল্ট মান দেবে। ৫. getProduct ৫ সেকেন্ডে উত্তর না দিলে timeout error দেবে, আর timer finally-তে বন্ধ করবে। শেষে setTimeout দিয়ে নকল async function বানিয়ে ৪টি পরীক্ষা দাও: সব সফল, রিভিউ ব্যর্থ, পণ্য ব্যর্থ, পণ্য ৬ সেকেন্ড দেরি — প্রতিটিতে কী দেখাবে আর কোনো unhandled rejection হচ্ছে কি না।

AI-এর লেখা promise-কোড — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১“Promise” আর “future” ধারণা JavaScript-এর অনেক আগের — ১৯৭০-এর দশকের গবেষণা থেকে E ভাষা হয়ে কীভাবে ব্রাউজারে পৌঁছাল?

ধারণাটির জন্ম সমান্তরাল হিসাবের গবেষণায়। ১৯৭৬ সালে Indiana University-র Daniel Friedman আর David Wise একটি গবেষণাপত্রে (“The Impact of Applicative Programming on Multiprocessing”) “promise” শব্দটি ব্যবহার করেন — এমন একটি মান যার হিসাব এখনো হয়নি, কিন্তু দরকার হলে হবে। প্রায় একই সময়ে, ১৯৭৭ সালে, MIT-র Henry Baker আর Carl Hewitt actor model-এর প্রসঙ্গে “future” নাম দেন: একটি হিসাব আলাদাভাবে চলতে থাকুক, আর তার ফলের জন্য একটি জায়গা ধরে রাখা হোক। ১৯৮০-এর দশকে Robert Halstead-এর MultiLisp-এ (future expr) ভাষার অংশ হয় — expr অন্য processor-এ চলে, আর ফল দরকার হওয়া মাত্র অপেক্ষা।

১৯৮৮ সালে Barbara Liskov আর Liuba Shrira PLDI সম্মেলনে “Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems” প্রকাশ করেন। সমস্যা ছিল নেটওয়ার্কে দূরের function ডাকা (RPC): প্রতিটি ডাকের উত্তরের জন্য বসে থাকলে নেটওয়ার্কের দেরি জমতে থাকে। তাঁদের promise ডাকার সাথে সাথে ফেরত আসত, আর অনেকগুলো ডাক একসাথে পাঠানো যেত — একে তাঁরা বলেছিলেন call stream. এখানেই প্রথম একটি টাইপ-যুক্ত, ব্যর্থতাসহ promise-এর পরিষ্কার নকশা।

JavaScript-এর promise-এর সরাসরি পূর্বপুরুষ E ভাষা — ১৯৯০-এর দশকের শেষে Mark S. Miller ও সহকর্মীদের তৈরি, নিরাপদ distributed প্রোগ্রামিংয়ের জন্য। E-তে promise ছিল মূল ধারণা, আর ছিল “promise pipelining”: ফল আসার আগেই সেই ফলের ওপর পরের বার্তা পাঠানো, যাতে নেটওয়ার্কে বারবার আসা-যাওয়া না লাগে। E-র “when” ধাঁচের handler আজকের then-এর মতোই — handler সবসময় পরে চলে, কখনো সাথে সাথে নয়। Mark Miller পরে Google-এ যোগ দেন আর TC39-এর সক্রিয় সদস্য হন — ES2015-এর promise নকশায় তাঁর প্রভাব স্পষ্ট।

মাঝের ধাপে ওয়েবের বাস্তব চাহিদা: Dojo Toolkit-এর Deferred, তারপর ২০০৯ সালের দিকে CommonJS গোষ্ঠীর “Promises/A” প্রস্তাব (Kris Zyp), Kris Kowal-এর Q library, jQuery-র Deferred — প্রত্যেকে নিজের মতো। এই বিশৃঙ্খলা থেকেই Promises/A+ (প্রশ্ন ২), আর শেষে ES2015-এ ভাষার নিজের Promise.

মূল উপলব্ধি: promise কোনো framework-এর ফ্যাশন নয় — চার দশকের পুরোনো উত্তর একটি মৌলিক প্রশ্নের: “ফল এখনো নেই, তবু কাজ কীভাবে এগিয়ে নেব?” সমান্তরাল হিসাব, distributed RPC আর ব্রাউজারের event loop — তিন জায়গায় একই উত্তর বারবার ফিরে এসেছে।

প্র ০২Promises/A+ (২০১২) কেন দরকার হয়েছিল? jQuery-র Deferred কোথায় “ভুল” ছিল, আর একটি সম্প্রদায়ের লেখা spec কীভাবে মানদণ্ডে পৌঁছাল?

২০১০-এর দশকের শুরুতে JavaScript-এ ডজনখানেক promise library — Q, when.js, RSVP, jQuery Deferred, Dojo — প্রত্যেকে then নামের method দেয়, কিন্তু আচরণ আলাদা। সবচেয়ে প্রভাবশালী ছিল jQuery-র Deferred (jQuery 1.5, ২০১১), কারণ তখন প্রায় প্রতিটি সাইটে jQuery. আর এর আচরণ অন্যদের থেকে মৌলিকভাবে ভিন্ন ছিল। শুরুর সংস্করণগুলোতে then নতুন promise ফেরত দিত না (রূপান্তরের জন্য আলাদা pipe লাগত, jQuery 1.8-এ then বদলায়)। Handler-এর ভেতরে throw করা error rejection-এ রূপান্তরিত হতো না — সোজা uncaught হয়ে যেত। আর promise আগেই শেষ থাকলে handler কখনো কখনো সাথে সাথে, synchronous-ভাবে চলত।

২০১২ সালের অক্টোবরে Domenic Denicola “You're Missing the Point of Promises” নামে একটি বহুল-পঠিত লেখা লেখেন। তাঁর যুক্তি: promise-এর আসল মূল্য callback-কে সুন্দর করা নয়, বরং synchronous কোডের দুটি মৌলিক জিনিস async জগতে ফিরিয়ে আনা — function-এর ফেরত মান আর throw. then নতুন promise না দিলে, আর throw-কে rejection না বানালে, এই মিল ভেঙে যায়। এই মতের অনুসারীরা — Brian Cavalier, Domenic Denicola ও আরও কয়েকজন — GitHub-এ খোলাভাবে Promises/A+ লেখেন: ছোট, নিখুঁত একটি spec, শুধু then-এর আচরণ নিয়ে, সাথে একটি স্বয়ংক্রিয় টেস্ট-স্যুট। যেকোনো library টেস্ট চালিয়ে দাবি করতে পারত “আমরা A+ মানি।”

A+-এর সবচেয়ে চতুর অংশ হলো thenable assimilation — then আছে এমন যেকোনো object-কে promise হিসেবে গ্রহণ করার নিয়ম। এর ফলে আলাদা library-র promise একে অপরের সাথে মেশে, কোনো কেন্দ্রীয় class ছাড়াই। TC39 যখন ES2015-এর জন্য Promise নকশা করে, তখন A+-কে ভিত্তি ধরে — তাই native Promise A+-সামঞ্জস্যপূর্ণ, আর পুরোনো library-গুলোর সাথেও মেশে। jQuery শেষ পর্যন্ত jQuery 3.0-তে (২০১৬) তার Deferred-কে A+-সামঞ্জস্যপূর্ণ করে।

একটি বড় শিক্ষা আছে এখানে: মানদণ্ড সবসময় কমিটির ঘরে জন্মায় না। বাস্তবে কয়েক বছরের অভিজ্ঞতা, খোলা আলোচনা আর একটি টেস্ট-স্যুট — এরপর ভাষার কমিটি সেটাকে আনুষ্ঠানিক রূপ দেয়।

মূল উপলব্ধি: promise-এর মূল্য তার আচরণের নিশ্চয়তায় — then সবসময় নতুন promise দেয়, throw সবসময় rejection হয়, handler সবসময় পরে চলে। এই নিশ্চয়তাগুলো ছাড়া promise শুধু callback-এর নতুন জামা।

প্র ০৩then একসাথে “map” আর “flatMap” দুটোর কাজ করে — promise-এর ভেতরে promise রাখা যায় না। ২০১৩ সালের “monad” বিতর্কে কী নিয়ে মতভেদ ছিল?

Array-র কথা ভাবুন (পর্ব ১৬)। [1, 2].map((x) => [x, x]) দেয় [[1, 1], [2, 2]] — ভেতরে array থেকে যায়। flatMap দেয় [1, 1, 2, 2] — এক স্তর চ্যাপ্টা করে। Promise-এর then দুটোই একসাথে: handler সাধারণ মান দিলে map-এর মতো, promise (বা thenable) দিলে flatMap-এর মতো — সেটার ভেতরে ঢুকে মান বের করে আনে। ফলে “একটি promise, যার মান আরেকটি promise” — এমন জিনিস native Promise দিয়ে বানানোই যায় না। Promise.resolve(Promise.resolve(5)) হলো সোজা ৫-এর promise.

Functional programming-এর ভাষায় যে কাঠামোর একটি “of” (মান মোড়ানো) আর একটি “chain/flatMap” থাকে আর কিছু নিয়ম মানে, তাকে monad বলে — Haskell-এর IO, Scala-র Future এর উদাহরণ। ২০১৩ সালে Promises/A+-এর repository-তে একটি আলোচনায় functional programming-এর দিক থেকে কয়েকজন প্রস্তাব দেন: then-এর বদলে (বা পাশাপাশি) আলাদা map আর chain রাখা হোক, আর স্বয়ংক্রিয় চ্যাপ্টা করা বাদ যাক — তাহলে promise গাণিতিকভাবে পরিষ্কার monad হবে, আর অন্য monad-এর জন্য লেখা সাধারণ কোড promise-এও চলবে। A+-এর লেখকেরা প্রস্তাবটি গ্রহণ করেননি; তাঁদের যুক্তি ছিল ব্যবহারিক — বিদ্যমান সব library আর ব্যবহারকারীর কোড then-এর বর্তমান আচরণের ওপর দাঁড়িয়ে, আর বেশিরভাগ মানুষের কাছে স্বয়ংক্রিয় চ্যাপ্টা করাই সুবিধাজনক। আলোচনাটি বেশ উত্তপ্ত হয়েছিল, আর এর পরপরই functional-ঘরানার লোকেরা আলাদা “Fantasy Land” spec শুরু করেন, যা JavaScript-এ algebraic কাঠামোর জন্য সাধারণ interface ঠিক করে।

TC39-এর ভেতরেও ES2015-এর খসড়া পর্যায়ে কিছুদিন আলাদা একটি chain-ধাঁচের method রাখার আলোচনা হয়েছিল, কিন্তু শেষ পর্যন্ত শুধু then-ই টিকে থাকে।

এর বাস্তব খরচ কোথায়? প্রথমত, যে মান “নিজেই একটি thenable”, তাকে কখনো promise-এর মান হিসেবে পৌঁছানো যায় না — শিকল তাকে খুলে ফেলবেই। দ্বিতীয়ত, প্রতিটি return-এ engine-কে দেখতে হয় মানটির then property আছে কি না — অর্থাৎ একটি property পড়া, যা getter বা Proxy হলে যেকোনো কোড চালাতে পারে। আবার সুবিধাটাও বিশাল: chaining-এর চার নিয়ম (অংশ ৪) এত সহজ হয়েছে ঠিক এই কারণেই — “promise return করলে শিকল অপেক্ষা করে।”

মূল উপলব্ধি: ভাষার নকশায় গাণিতিক পরিচ্ছন্নতা আর দৈনন্দিন সুবিধা প্রায়ই মুখোমুখি দাঁড়ায়। JavaScript সুবিধা বেছেছে — তার দাম হলো কিছু কোণার ক্ষেত্রে অদ্ভুত আচরণ, যা জানা থাকলে আর অবাক করে না।

প্র ০৪ES2015-এ Promise ভাষার অংশ হলো — তবু অনেক বছর Bluebird-এর মতো library native promise-এর চেয়ে দ্রুত ছিল কেন? আর engine কীভাবে সেই ব্যবধান মুছল?

ES2015 (ES6) Promise-কে ভাষার অংশ করে, আর Chrome, Firefox, Safari আর Node.js কয়েক বছরের মধ্যে native সংস্করণ দেয়। কিন্তু প্রথম দিকের native promise ধীর ছিল — অনেক benchmark-এ Petka Antonov-এর Bluebird library কয়েক গুণ দ্রুত আর কম memory খরচ করত। একটি library কীভাবে engine-কে হারায়? কারণ engine-গুলো প্রথমে Promise লিখেছিল মানদণ্ডের প্রতিটি ধাপ হুবহু অনুসরণ করে, অনেক সময় JavaScript-এই, আর প্রতিটি then-এর জন্য কয়েকটি করে ভেতরের object আর closure তৈরি করত। Bluebird শুরু থেকে গতির জন্য নকশা করা — কম object, কম closure, বিশেষ fast path. Node.js সার্ভারে প্রতি সেকেন্ডে লক্ষাধিক promise তৈরি হয়, সেখানে এই পার্থক্য সত্যি চোখে পড়ত।

পরিবর্তন আসে কয়েক ধাপে। V8 দল promise-এর বাস্তবায়ন engine-এর নিজস্ব দ্রুত builtin হিসেবে নতুন করে লেখে। তারপর async/await (ES2017) জনপ্রিয় হলে দেখা গেল প্রতিটি await-এর পেছনে মানদণ্ড অনুযায়ী কয়েকটি বাড়তি microtask আর একটি “throwaway” promise লাগছে। ২০১৮ সালে V8 ব্লগে “Faster async functions and promises” লেখায় দলটি দেখায় কীভাবে বাড়তি ধাপগুলো বাদ দেওয়া যায় — আর এজন্য মানদণ্ডেও একটি ছোট পরিবর্তন প্রস্তাব করা হয়, যা পরে গৃহীত হয়: native promise-কে await করলে আর অকারণে নতুন promise-এ মোড়ানো হয় না। ফল হিসেবে async কোড অনেক দ্রুত হয়, আর কিছু কোণার ক্ষেত্রে microtask-এর ক্রমও সামান্য বদলায় — যে কারণে “আউটপুট কী হবে” ধাঁধার উত্তর পুরোনো আর নতুন engine-এ আলাদা হতে পারে।

সাথে আসে debugging-এর সুবিধা: V8 এখন async stack trace দেখাতে পারে — একটি await-এর ওপারে কে ডেকেছিল, সেটাও stack-এ। এটা library দিয়ে করা কঠিন, কারণ engine-এর ভেতরের তথ্য লাগে। Unhandled rejection শনাক্ত করাও (অংশ ৫) engine-এর ভেতরের একটি hook দিয়ে হয়।

আজ native promise-ই সাধারণভাবে সেরা পছন্দ; Bluebird-এর নিজের documentation-ও নতুন প্রজেক্টে native promise ব্যবহার করতে বলে।

মূল উপলব্ধি: একটি ফিচার মানদণ্ডে আসা মানে যাত্রার শুরু, শেষ নয়। প্রথমে সঠিক, তারপর দ্রুত — আর কখনো কখনো দ্রুত করতে মানদণ্ডকেও একটু বদলাতে হয়।

প্র ০৫একটি promise বাতিল (cancel) করা যায় না কেন? ২০১৬ সালের “cancelable promises” প্রস্তাব কেন প্রত্যাহার হলো, আর AbortController কীভাবে অন্য পথে সমস্যাটা মেটাল?

ব্যবহারকারী একটি search box-এ “মোবা” লিখলেন, request গেল; তারপর লিখলেন “মোবাইল” — নতুন request. পুরোনোটার ফল আর দরকার নেই, বরং দেরিতে এলে ভুল ফল দেখাবে। স্বাভাবিক চাওয়া: পুরোনো promise বাতিল করা। কিন্তু promise-এর কোনো cancel() নেই — কেন?

মূল কারণ নকশায়। একটি promise একটি মানের প্রতিনিধি, কোনো কাজের নয়। যে কাজ মান তৈরি করছে, promise তাকে চেনে না। তার ওপর একটি promise-এর অনেক “গ্রাহক” থাকতে পারে — একই promise পাঁচ জায়গায় then দিয়ে ব্যবহৃত। একজন গ্রাহক বাতিল করলে বাকি চারজনের কী হবে? আর বাতিল হলে promise-এর অবস্থা কী — rejected? নাকি তৃতীয় একটি নতুন অবস্থা, “canceled”? তৃতীয় অবস্থা আনলে প্রতিটি শিকল, প্রতিটি combinator, প্রতিটি library-র আচরণ নতুন করে ভাবতে হয়।

Bluebird নিজস্ব cancellation দিয়েছিল, আর এর নকশা কয়েকবার বদলাতে হয়েছিল — সমস্যাটা যে কঠিন, তার প্রমাণ। ২০১৬ সালে Domenic Denicola TC39-এ “cancelable promises” প্রস্তাব আনেন, যেখানে cancel token আর একটি তৃতীয় ধরনের ফলাফলের ধারণা ছিল। প্রস্তাবটি প্রাথমিক পর্যায়ে (stage 1) ছিল, কিন্তু ২০১৬ সালের শেষে তিনি এটি প্রত্যাহার করেন — তিনি জানান যে তাঁর নিয়োগকর্তা Google-এর ভেতরে প্রস্তাবটি নিয়ে আপত্তি ছিল, আর বিস্তারিত বলেননি। এর পরে TC39-এ আর কোনো cancellation প্রস্তাব মানদণ্ডে পৌঁছায়নি।

সমাধান এলো ভাষার বাইরে থেকে — DOM মানদণ্ডে, ২০১৭ সালে: AbortController আর AbortSignal. ধারণাটা আলাদা: promise-কে বাতিল নয়, কাজকে একটি signal দেওয়া হয়। fetch(url, { signal }) — controller-এর abort() ডাকলে fetch নিজের request থামায় আর তার promise একটি AbortError দিয়ে rejected হয়। নতুন কোনো অবস্থা নেই, চেনা rejection-এর পথই। একটি signal অনেক কাজকে দেওয়া যায়, আর কাজটি নিজে ঠিক করে কীভাবে থামবে। পরে এসেছে AbortSignal.timeout(ms) আর একাধিক signal মেলানোর AbortSignal.any(), আর Node.js-এর অনেক API-ও এখন signal নেয়। বিস্তারিত পর্ব ৩৬-এ।

মূল উপলব্ধি: কখনো একটি সমস্যা ভুল জায়গায় সমাধান করতে গেলে আটকে যায়। “মান”-কে বাতিল করা অর্থহীন; “কাজ”-কে থামার সংকেত দেওয়াই সঠিক স্তর — আর সেই বিভাজনই AbortController-কে সরল আর টেকসই করেছে।

অনুশীলন

বেশিরভাগ কাজ browser console-এ (F12) কয়েকটি লাইন লিখেই করা যায় — ফল আগে অনুমান করুন, তারপর মিলিয়ে নিন।

  1. ক্রম অনুমান করুন: console.log("1"); setTimeout(() => console.log("2"), 0); Promise.resolve().then(() => console.log("3")); new Promise((r) => { console.log("4"); r(); }).then(() => console.log("5")); console.log("6");

    1, 4, 6, 3, 5, 2. Synchronous অংশ আগে: 1, তারপর executor সাথে সাথে চলে বলে 4, তারপর 6. Script শেষ হলে microtask — জোড়ার ক্রমে 3, তারপর 5. সবশেষে timer-এর task: 2. বিলম্ব ০ হলেও timer microtask-এর পরে।

  2. বাগ খুঁজুন: checkStock(item).then((s) => { reserve(s); }).then((r) => charge(r)).catch(showError); — reserve ২ সেকেন্ড নেয় আর কখনো কখনো ব্যর্থ হয়। কী কী ভুল ঘটবে, আর ঠিক করবেন কীভাবে?

    প্রথম then-এ { } আছে কিন্তু return নেই। ফল: (১) দ্বিতীয় then reserve শেষ হওয়ার অপেক্ষা না করে সাথে সাথে চলে, আর r হয় undefined — charge ভুল argument পায়, হয়তো রিজার্ভ ছাড়াই টাকা কাটে। (২) reserve ব্যর্থ হলে তার rejection এই শিকলে নেই, তাই showError কিছুই জানে না — unhandled rejection. ঠিক: .then((s) => reserve(s)) বা সরাসরি .then(reserve).

  3. নিজের allSettled (কোড লিখুন): শুধু Promise.all আর map ব্যবহার করে mySettled(promises) লিখুন, যা Promise.allSettled-এর মতো { status, value }/{ status, reason } array দেয় আর কখনো rejected হয় না।

    মূল কৌশল: প্রতিটি promise-কে আগে এমন promise-এ রূপান্তর করুন যা কখনো ব্যর্থ হয় না — const mySettled = (ps) => Promise.all(ps.map((p) => Promise.resolve(p).then((value) => ({ status: "fulfilled", value }), (reason) => ({ status: "rejected", reason }))));. প্রতিটি ভেতরের promise-এর onRejected একটি object return করে, তাই সেটি fulfilled — ফলে Promise.all-এর fail-fast কখনো ঘটে না। এখানে then(ok, fail) দুই-argument রূপটিই ঠিক, কারণ আমরা শুধু p-এর ব্যর্থতা ধরতে চাই। Promise.resolve(p) সাধারণ মানও গ্রহণ করে। যাচাই: mySettled([Promise.reject(new Error("x")), 5]).then(console.log).

  4. AI অডিট: যেকোনো AI-কে বলুন “JavaScript-এ একটি dashboard-এর জন্য বিক্রি, স্টক আর রিভিউ তিনটি API থেকে ডেটা এনে দেখানোর কোড লেখো, promise দিয়ে।” তারপর অংশ ৮-এর চেকলিস্ট দিয়ে উত্তরটি পরীক্ষা করুন।

    সাধারণত দেখা যায়: (১) Promise.all — একটি API বন্ধ হলে পুরো dashboard ফাঁকা; এখানে allSettled দরকার। (২) শেষে .catch((err) => console.log(err)) — ব্যবহারকারী কিছুই দেখেন না। (৩) কখনো nested then বা then-এর ভেতরে return ছাড়া কোড। (৪) timeout নেই — একটি API ঝুলে থাকলে allSettled-ও চিরকাল অপেক্ষা করবে, তাই প্রতিটির সাথে withTimeout লাগে। ঠিক করতে বলুন: “allSettled ব্যবহার করো, প্রতিটি widget-এর জন্য আলাদা ‘তথ্য পাওয়া যাচ্ছে না’ অবস্থা দাও, প্রতিটি request-এ ৫ সেকেন্ডের timeout দাও, আর error গিলবে না।”

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমnew Promise দিয়ে তৈরি একটি promise থেকে then-এর handler চলা পর্যন্ত ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Executor সাথে সাথে চলে আর একটি async কাজ (যেমন timer) শুরু করে
  2. .then(handler) handler-টিকে promise-এর অপেক্ষার তালিকায় রাখে আর নতুন promise ফেরত দেয়
  3. Synchronous কোড শেষ হয়, call stack খালি হয়
  4. Async কাজ শেষে resolve(value) — promise fulfilled হয়
  5. Handler microtask লাইনে যায়, তারপর চলে আর value পায়

একটি promise fulfilled হওয়ার পরে তার executor-এ থাকা reject(new Error("x")) ডাকা হলে কী হয়?

  • promise rejected হয়ে যায়
  • TypeError ছোড়া হয়
  • কিছুই হয় না — ডাকটি উপেক্ষিত
  • promise আবার pending হয়ে যায়

Promise.resolve(1).then((x) => { x + 1; }).then((v) => console.log(v)) কী ছাপে?

  • undefined
  • 2
  • 1
  • কিছুই না — error হয়

একটি dashboard-এ পাঁচটি স্বাধীন widget-এর ডেটা একসাথে আনতে হবে, আর একটি ব্যর্থ হলেও বাকিগুলো দেখাতে হবে। কোন combinator সবচেয়ে উপযুক্ত?

  • Promise.all
  • Promise.allSettled
  • Promise.race
  • Promise.any

Promise.any-কে দেওয়া সবগুলো promise ব্যর্থ হলে কী হয়?

  • undefined দিয়ে fulfilled হয়
  • প্রথম ব্যর্থতার কারণ দিয়ে rejected হয়
  • চিরকাল pending থাকে
  • AggregateError দিয়ে rejected হয়, কারণগুলো errors-এ

new Promise(executor) লিখলে executor function কখন চলে?

  • সাথে সাথে, synchronous-ভাবে
  • পরের microtask হিসেবে
  • প্রথম .then লাগানোর পরে
  • পরের timer task-এ

একটি promise শিকলে .catch((e) => "ডিফল্ট")-এর পরে আরেকটি .then((v) => console.log(v)) আছে। আগের কোনো ধাপ ব্যর্থ হলে কী ছাপা হবে?

  • কিছুই না — শিকল rejected থাকে
  • Error object টি
  • ডিফল্ট
  • undefined

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Promise ভবিষ্যতের মানের রসিদ: pending থেকে ঠিক একবার fulfilled (মান) বা rejected (কারণ); resolved মানেই fulfilled নয়।
  2. Executor সাথে সাথে চলে, handler সবসময় পরে — microtask হিসেবে, এমনকি promise আগেই শেষ হয়ে থাকলেও।
  3. প্রতিটি then নতুন promise দেয়: মান return → পরের ধাপ পায়; promise return → অপেক্ষা; throw → rejection; return ভুলে গেলে undefined.
  4. Rejection নিচে নেমে প্রথম catch-এ থামে; catch মান return করলে শিকল আবার সুস্থ। শেষে একটি অর্থপূর্ণ catch — খালি .catch(() => {}) নয়।
  5. all সব চায়, allSettled সবার খবর দেয়, race প্রথম শেষ, any প্রথম সফল; কোনোটিই চলমান কাজ বাতিল করে না।

পরের পর্বে: checkStock(item).then(reserve).then(charge).then(sendSms).catch(…) — এই শিকলটাই যদি সাধারণ synchronous কোডের মতো লাইনের পর লাইন লেখা যেত, আর চেনা try/catch দিয়ে ভুল ধরা যেত? async/await ঠিক তাই করে — promise-এর ওপরেই দাঁড়িয়ে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান