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

async/await — promise-এর কোড সহজে লেখা আর ভুল সামলানো

JavaScript async/await — Async Functions, Where await Pauses, try/catch/finally, Sequential vs Parallel Awaits, for await…of, Top-Level Await and Async Debugging

This Bangla lesson rewrites promise chains with async/await and explains what really happens: async functions always return promises, await pauses only its own function, try/catch/finally for errors, sequential versus parallel awaits with Promise.all, why forEach ignores async callbacks, for await…of, return await, fire-and-forget risks, top-level await and async stack traces in DevTools.

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

  • async function কী ফেরত দেয়, আর await ঠিক কোথায় থামে ও কখন আবার চলে — log-এর ক্রম মিলিয়ে
  • পর্ব ৩৩-এর then-শিকল await দিয়ে নতুন করে লেখা, আর try/catch/finally ও cause দিয়ে ভুল সামলানো
  • লুপে await-এর ফাঁদ: কখন একটার পর একটা ঠিক, কখন Promise.all, allSettled আর race
  • async forEach-এর বাগ, for await…of, top-level await, async method আর static factory
  • return বনাম return await, fire-and-forget-এর বিপদ, DevTools-এ async stack trace আর AI-এর লেখা async কোড যাচাই

১ · async/await কেন — promise-এর কোডকে সাধারণ কোডের মতো পড়া

পর্ব ৩৩-এ একটি অর্ডারের কাজ promise-এর শিকল দিয়ে লিখেছিলেন — কার্ট যাচাই, স্টক রাখা, টাকা কাটা, SMS পাঠানো। কোডটা কাজ করে, কিন্তু পড়তে গেলে চোখকে .then থেকে .then-এ লাফাতে হয়। আর কোনো এক ধাপের ফল যদি তিন ধাপ পরে লাগে, তখন সেটা বাইরের কোনো variable-এ রেখে দিতে হয়, বা শিকলের ভেতরে আরেকটা শিকল বানাতে হয়:

function placeOrder(cart) {
  let reservation;
  return validateCart(cart)
    .then(() => reserveStock(cart))
    .then((r) => { reservation = r; return charge(cart.total); })
    .then((txn) => sendSms(cart.phone, `রসিদ ${txn}, স্টক ${reservation.id}`))
    .catch((err) => { showMessage("অর্ডার নেওয়া যায়নি"); throw err; });
}

একই কাজ async/await দিয়ে:

async function placeOrder(cart) {
  try {
    await validateCart(cart);
    const reservation = await reserveStock(cart);
    const txn = await charge(cart.total);
    await sendSms(cart.phone, `রসিদ ${txn}, স্টক ${reservation.id}`);
  } catch (err) {
    showMessage("অর্ডার নেওয়া যায়নি");
    throw err;
  }
}

দুটি কোড হুবহু একই কাজ করে, একই promise তৈরি করে, একই সময় নেয়। পার্থক্য শুধু মানুষের চোখে: দ্বিতীয়টি ঠিক সেই ক্রমে পড়া যায় যে ক্রমে ঘটনা ঘটে। প্রতিটি ধাপের ফল একটি সাধারণ const-এ থাকে, আর ভুল ধরা হয় পর্ব ২৯-এর চেনা try/catch দিয়ে।

মূল ধারণা

async/await হলো promise-এর ওপর বসানো একটি syntax-এর সুবিধা (ES2017). দুটি নিয়ম মনে রাখলেই পুরো ছবি পরিষ্কার: (১) async function সবসময় একটি promise ফেরত দেয়। (২) await শুধু সেই function-টিকে থামিয়ে রাখে, যতক্ষণ না promise মীমাংসা (settle) হয়। পূর্ণ হলে await-এর জায়গায় মানটি বসে আর function আবার চলে। ব্যর্থ হলে ঠিক সেই লাইনে error ছোড়া হয়, যেন সেখানে throw লেখা ছিল। এই সময়ে বাকি প্রোগ্রাম — ক্লিক, টাইমার, অন্য function — থেমে থাকে না।

একটি চায়ের দোকানের কথা ভাবুন। দোকানি এক ক্রেতার জন্য চা বসালেন। পানি ফুটতে তিন মিনিট লাগবে। তিনি কেতলির সামনে দাঁড়িয়ে থাকেন না — অন্য ক্রেতাকে বিস্কুট দেন, টাকা ফেরত দেন। পানি ফুটলে ফিরে এসে ঠিক যেখানে থেমেছিলেন সেখান থেকে চা বানানো শেষ করেন। প্রথম ক্রেতার “চা বানানো” কাজটি থেমে ছিল (await), দোকান থেমে ছিল না। আর দোকানি একজনই — JavaScript-এর একটিমাত্র thread-এর মতো (পর্ব ৫)।

async/await প্রথম মূলধারায় আনে C# 5 (২০১২)। তারপর Python 3.5 (২০১৫), JavaScript ES2017, Rust (২০১৯), Swift (২০২১) — প্রায় একই চেহারায়। JavaScript-এ মানদণ্ডে আসার আগেই লোকে generator আর promise মিলিয়ে একই জিনিস বানিয়ে ব্যবহার করত। পুরো গল্প ভাবনার প্রশ্ন ১ ও ২-এ।

২ · async function — যা-ই করুক, ফেরত দেয় একটি promise

কোনো function-এর আগে async লিখলে তিনটি জিনিস বদলায়:

  • ফেরত মান promise-এ মোড়ানো হয়। async function f() { return 5; } — f() ডাকলে 5 পাবেন না, পাবেন একটি promise, যা 5 দিয়ে পূর্ণ। মান পেতে হয় await f(), নয়তো f().then(...).
  • throw মানে ব্যর্থ promise. ভেতরে error ছোড়া হলে সেটি ডাকার জায়গায় সাথে সাথে ছিটকে আসে না — ফেরত দেওয়া promise-টি সেই error দিয়ে rejected হয়। তাই f() ডাকার চারপাশে সাধারণ try/catch কিছুই ধরবে না, যদি না আপনি await করেন।
  • ভেতরে await লেখা যায়। সাধারণ function-এ await লিখলে SyntaxError: await is only valid in async functions and the top level bodies of modules (V8-এর বার্তা)।

আর যদি async function নিজেই একটি promise ফেরত দেয়? তখন বাড়তি মোড়ক হয় না — বাইরের promise ভেতরেরটির ফল অনুসরণ করে। promise-এর ভেতরে promise কখনো আপনার হাতে আসে না।

await-এর ডান পাশে কী থাকতে পারে

সাধারণত একটি promise. কিন্তু যেকোনো মানই দেওয়া যায়: await 5 লিখলে মানটি আগে একটি পূর্ণ promise-এ মোড়ানো হয়, তারপর অপেক্ষা। ফলে 5-ই পাবেন, কিন্তু সাথে সাথে নয় — function তবুও এক মুহূর্তের জন্য থামে, আর বাকি কাজ পরে একটি microtask হিসেবে চলে। আর যে object-এর একটি then method আছে (thenable, যেমন পুরোনো library-র promise), await সেটাকেও promise-এর মতো সামলায়।

কোন কোন রূপে লেখা যায়

async function loadUser(id) { /* ... */ }            // declaration
const loadCart = async function () { /* ... */ };   // expression
const loadOffers = async () => { /* ... */ };         // async arrow function
const shop = {
  async loadMenu() { /* ... */ },                     // object-এর method
};
class Api {
  async get(path) { /* ... */ }                       // class-এর method
  static async create() { /* ... */ }                 // static method
}
button.addEventListener("click", async () => { /* ... */ });

একটি জায়গায় async চলে না: class-এর constructor. async constructor() লিখলে V8 বলে SyntaxError: Class constructor may not be an async method. কারণ new সবসময় তৈরি হওয়া object-টিই ফেরত দেয়, promise নয়। সমাধান অংশ ৭-এ।

৩ · await কী থামায়, কী থামায় না — ধাপে ধাপে

async/await বোঝার সবচেয়ে বড় বাধা একটি ভুল ধারণা: “await মানে প্রোগ্রাম এখানে দাঁড়িয়ে থাকে।” না। নিচের ডায়াগ্রামে একটি async function দুবার await করে। খেয়াল করুন কোন log কখন আসে। (wait(ms, value) একটি ছোট সাহায্যকারী function — ms মিলিসেকেন্ড পর value দিয়ে পূর্ণ হয় এমন promise দেয়, নিচের চালানো যায় এমন কোডে আছে।)

1async function loadProfile() {2  console.log("২. profile আনা শুরু");3  const user = await wait(50, "রিনা");4  console.log("৫. user এল:", user);5  const orders = await wait(30, 3);6  console.log("৬. অর্ডার এল:", orders);7  return `${user}: ${orders}টি অর্ডার`;8}9console.log("১. শুরু");10const job = loadProfile();11console.log("৩. বাকি প্রোগ্রাম চলছে, job হলো:", job);12setTimeout(() => console.log("৪. অন্য একটি টাইমার (২০ms)"), 20);13job.then((text) => console.log("৭. ফল:", text));
এই মুহূর্তে
ঘড়ি
loadProfile
job
call stack
  1. সাধারণ কোড আগে। লাইন ১-৮ শুধু function তৈরি করে, কিছু চালায় না। লাইন ৯ ছাপে ১. শুরু.
  2. async function প্রথমে সাধারণ function-এর মতোই চলে। লাইন ১০-এ ডাক পড়ল, call stack-এ loadProfile বসল, আর লাইন ২ সাথে সাথে ছাপল ২. profile আনা শুরু. প্রথম await পর্যন্ত async function-এর কোড synchronous — এটা অনেকেই জানেন না।
  3. প্রথম await: function থামল, কিন্তু ফিরে এল। লাইন ৩-এ wait(50, …) একটি অপেক্ষমাণ promise দিল। await সেই promise-কে বলে রাখল: “মীমাংসা হলে আমাকে এখান থেকে আবার চালাবেন।” তারপর loadProfile-এর অবস্থা (local variable, কোন লাইনে আছে) সরিয়ে রাখা হলো, stack থেকে frame নামল, আর ডাকার জায়গায় ফেরত গেল একটি pending promise — সেটাই job.
  4. বাকি প্রোগ্রাম থামেনি। লাইন ১১ ছাপল ৩. … — এখানে job একটি promise, মান নয় (runner দেখায় Promise { ... }, Node.js দেখায় Promise { <pending> })। লাইন ১২ একটি ২০ ms-এর টাইমার বসাল, লাইন ১৩ job-এর জন্য একটি then বসাল। মূল কোড শেষ, stack খালি। এখন event loop-এর পালা (পর্ব ৩১)।
  5. অপেক্ষার সময় অন্য কাজ চলে। ২০ ms-এ টাইমার বাজল, event loop তার callback চালাল: ৪. অন্য একটি টাইমার. loadProfile তখনো লাইন ৩-এ থেমে — কিন্তু কোনো CPU সময় নিচ্ছে না, কাউকে আটকাচ্ছে না।
  6. Promise পূর্ণ হলো: ঠিক সেই জায়গা থেকে আবার চলা। ৫০ ms-এ wait পূর্ণ হলো "রিনা" দিয়ে। function-টির বাকি অংশ একটি microtask হিসেবে লাইনে দাঁড়াল আর সাথে সাথে চলল: await-এর জায়গায় বসল "রিনা", ছাপা হলো ৫. user এল: রিনা. তারপর লাইন ৫-এ আবার await — আবার থামা, আবার stack খালি।
  7. শেষ: return মানে promise পূর্ণ। ৮০ ms-এ দ্বিতীয় অপেক্ষা শেষ। লাইন ৬ ছাপল, লাইন ৭-এর return মানটি দিয়ে job পূর্ণ করল। তাতে লাইন ১৩-এর callback আরেকটি microtask হিসেবে চলল: ৭. ফল: রিনা: 3টি অর্ডার. লক্ষ করুন: log-এর নম্বর ১, ২, ৩… — কিন্তু কোডে লেখা আছে ১, ২, ৫, ৬, ৭ ওপরে আর ৩, ৪ নিচে।
await = “এই function এখানে অপেক্ষা করবে” — “প্রোগ্রাম এখানে অপেক্ষা করবে” নয়

এবার নিজে চালিয়ে মিলিয়ে নিন। wait-এর বিলম্বগুলো (২০, ৫০, ৩০ ms) আলাদা রাখা হয়েছে, যাতে ক্রম প্রতিবার একই আসে:

const wait = (ms, value) =>
  new Promise((resolve) => setTimeout(() => resolve(value), ms));

async function loadProfile() {
  console.log("২. profile আনা শুরু");
  const user = await wait(50, "রিনা");      // এখানে থামে, বাকিরা চলে
  console.log("৫. user এল:", user);
  const orders = await wait(30, 3);         // আবার থামে
  console.log("৬. অর্ডার এল:", orders);
  return `${user}: ${orders}টি অর্ডার`;
}

console.log("১. শুরু");
const job = loadProfile();
console.log("৩. বাকি প্রোগ্রাম চলছে, job হলো:", job);
setTimeout(() => console.log("৪. অন্য একটি টাইমার (২০ms)"), 20);
job.then((text) => console.log("৭. ফল:", text));

আউটপুট ঠিক ১ থেকে ৭ ক্রমে আসে। তিনটি জিনিস খেয়াল করুন। প্রথমত, ২. আসে ৩.-এর আগে — async function প্রথম await পর্যন্ত সাথে সাথে চলে। দ্বিতীয়ত, job ছাপলে আসে Promise { ... } (এই পাতার runner-এর লেখা; Node.js বা DevTools-এ Promise {<pending>}) — async function-এর ফল তখনো আসেনি। তৃতীয়ত, ৪.-এর টাইমার লেখা হয়েছে সবার শেষে, তবুও চলেছে ৫.-এর আগে — কারণ loadProfile যখন থেমে ছিল, তখন event loop অন্য কাজ করছিল। wait(30, 3)-এর 3 একটি number, তাই আউটপুটে Latin অঙ্কে আসে।

ভেতরে কী ঘটে (পর্ব ৩১-এর ভাষায়): await p-কে মোটামুটি এভাবে ভাবতে পারেন — “p.then(বাকি অংশ, বাকি অংশে throw), আর এখনই ফিরে যাওয়া।” তাই await-এর পরের কোড সবসময় একটি microtask হিসেবে চলে, এমনকি promise আগে থেকেই পূর্ণ হলেও। এজন্যই await null-এর পরের লাইন চলে বর্তমান synchronous কোড শেষ হওয়ার পরে, কিন্তু যেকোনো setTimeout(…, 0)-এর আগে। Engine function-টির অবস্থা ধরে রাখে ঠিক পর্ব ২৭-এর generator-এর মতো — async function আসলে একধরনের coroutine.

৪ · ভুল সামলানো — try, catch, finally আবার কাজে

পর্ব ২৯-এ দেখেছিলেন, setTimeout-এর callback-এ ছোড়া error বাইরের try/catch ধরতে পারে না, কারণ callback চলে পরে, অন্য call stack-এ। promise-এর .catch এই সমস্যার একটি সমাধান। await আরেকটি, আরও স্বাভাবিক সমাধান দেয়: await করা promise ব্যর্থ হলে, await-এর লাইনেই error ছোড়া হয়। function তখনো চালু, তার try block তখনো সক্রিয় — তাই চেনা catch ধরে ফেলে।

একটি চেকআউট — ব্যবহারকারীকে স্পষ্ট বার্তা

অনলাইন দোকানের চেকআউট বাটন চাপলে চারটি ধাপ: কার্ট যাচাই → স্টক আলাদা রাখা → কার্ড থেকে টাকা কাটা → SMS-এ রসিদ। কোথায় ব্যর্থ হলো, তার ওপর নির্ভর করে ব্যবহারকারীকে আলাদা কথা বলতে হয়:

async function onCheckoutClick(cart) {
  button.disabled = true;
  try {
    await validateCart(cart);                    // ভুল হলে ValidationError
    const hold = await reserveStock(cart);
    const txn = await charge(cart.total);        // ভুল হলে PaymentError
    sendReceiptSms(cart.phone, txn).catch(logError);   // ব্যর্থ হলেও অর্ডার বাতিল নয়
    showMessage(`অর্ডার সফল! রসিদ নম্বর ${txn}`);
  } catch (err) {
    if (err instanceof ValidationError) showMessage(err.message);
    else if (err instanceof PaymentError) showMessage("পেমেন্ট হয়নি — আপনার টাকা কাটা হয়নি");
    else { showMessage("সমস্যা হয়েছে, একটু পরে আবার চেষ্টা করুন"); logError(err); }
  } finally {
    button.disabled = false;                     // সফল হোক বা ব্যর্থ
  }
}

লক্ষ করুন SMS-এর লাইনটি। রসিদের SMS না গেলে অর্ডার বাতিল হওয়া উচিত নয়, আর এর জন্য ব্যবহারকারীকে অপেক্ষাও করাতে চাই না। তাই এখানে ইচ্ছা করে await নেই — কিন্তু .catch(logError) আছে। এই পার্থক্য অংশ ৭-এ আবার আসবে। finally নিশ্চিত করে যে বাটন আবার চালু হবেই, কোন পথে বের হলেন তাতে কিছু যায় আসে না।

প্রতি ধাপে আলাদা catch, নাকি সবার জন্য একটি?

🧺 একটি try, পুরো কাজের জন্য

যখন যেকোনো ধাপ ব্যর্থ হলে পুরো কাজটাই ব্যর্থ, আর প্রতিক্রিয়াও একই। কোড পরিষ্কার থাকে। কোন ধাপে ভুল হলো, তা জানা যায় error-এর ধরন (instanceof) দেখে।

বেশিরভাগ ক্ষেত্রে এটাই

🔧 আলাদা try, নির্দিষ্ট ধাপের জন্য

যখন কোনো একটি ধাপের ব্যর্থতায় বিকল্প পথ আছে — যেমন অফার আনা না গেলে অফার ছাড়াই পাতা দেখানো। শুধু সেই await-এর চারপাশে ছোট try, বাকিটা চলে স্বাভাবিকভাবে।

fallback থাকলে

ছোট আলাদা catch-এর একটি সহজ রূপ হলো await-এর সাথে সরাসরি .catch লাগানো: const offers = await loadOffers().catch(() => []); — অফার আনা ব্যর্থ হলে খালি তালিকা। async/await আর then/catch একই promise-এর দুই রূপ, তাই এভাবে মেশানো সম্পূর্ণ বৈধ। কিন্তু একই function-এ এলোমেলোভাবে দুই ধাঁচ মেশালে পড়া কঠিন হয় — ছোট, এক লাইনের fallback ছাড়া একটি ধাঁচই রাখুন।

প্রসঙ্গ যোগ করুন, আসলটা রেখে দিন

নিচু স্তরের error (“connection reset”) ওপরের স্তরে প্রায়ই অর্থহীন। পর্ব ২৯-এর মতো cause দিয়ে মুড়ে দিন: throw new Error("অর্ডার #৫১২ সেভ হয়নি", { cause: err }). log-এ তখন দুটোই থাকে — কী করতে গিয়ে ব্যর্থ, আর আসল কারণ কী।

যে ফাঁদে প্রায় সবাই একবার পড়েন: await ছাড়া try/catch কিছুই ধরে না। try { saveOrder(); } catch (e) { … } — এখানে saveOrder() async হলে সাথে সাথে একটি promise ফেরত দেয়, try শেষ। পরে সেই promise ব্যর্থ হলে catch অনেক আগেই পার হয়ে গেছে, আর ফল একটি unhandled rejection. সমাধান একটি শব্দ: await saveOrder();

৫ · লুপে await-এর ফাঁদ — একটার পর একটা, নাকি একসাথে?

একটি অ্যাডমিন dashboard খুললে তিনটি widget লোড হয়: আজকের বিক্রি, স্টক, নতুন বার্তা। তিনটি আলাদা API, কেউ কারও ওপর নির্ভর করে না। ধরুন প্রতিটি ১০০ ms নেয়। সবচেয়ে স্বাভাবিকভাবে লেখা কোড কত সময় নেবে?

1// ক) একটার পর একটা2const sales = await getSales();   // ১০০ ms3const stock = await getStock();   // ১০০ ms4const msgs  = await getMsgs();    // ১০০ ms5// খ) একসাথে শুরু, একসাথে অপেক্ষা6const [s, t, m] = await Promise.all([7  getSales(), getStock(), getMsgs(),8]);
নকল ঘড়ি
ঘড়ি
চলমান অনুরোধ
মোট অপেক্ষা
  1. প্রথম await: শুধু বিক্রির অনুরোধ। getSales() অনুরোধ পাঠাল, await function-টিকে থামাল। স্টক আর বার্তার অনুরোধ এখনো পাঠানোই হয়নি — কারণ সেই লাইনগুলোতে এখনো পৌঁছানো হয়নি। ১০০ ms পরে বিক্রির ফল এল।
  2. তারপর স্টক। এখন লাইন ৩ চলল, স্টকের অনুরোধ গেল — আরও ১০০ ms. স্টকের সার্ভার এতক্ষণ বসে ছিল, অথচ এর সাথে বিক্রির কোনো সম্পর্কই নেই।
  3. তারপর বার্তা — মোট ৩০০ ms. তিনটি স্বাধীন কাজ, কিন্তু কোড তাদের একটি সারিতে দাঁড় করিয়েছে। কোনো error নেই, ফলও ঠিক — শুধু তিন গুণ ধীর। এজন্যই এই বাগ সহজে চোখে পড়ে না।
  4. Promise.all: আগে সব শুরু, তারপর অপেক্ষা। লাইন ৭-এ তিনটি function ডাকা হলো একই মুহূর্তে — তিনটি অনুরোধ একসাথে চলে গেল, আর তিনটি promise একটি array-তে। তারপর একটিমাত্র await, Promise.all-এর ফলের জন্য (পর্ব ৩৩)।
  5. মোট সময় = সবচেয়ে ধীরটির সময়। ১০০ ms-এ তিনটিই শেষ, আর Promise.all ফল দেয় একই ক্রমে, যে ক্রমে array-তে দেওয়া হয়েছিল — কে আগে শেষ হলো তাতে কিছু যায় আসে না। destructuring (পর্ব ১৭) দিয়ে তিনটি নামে নেওয়া হলো। ৩০০ থেকে ১০০ ms — একটি লাইনও যোগ না করে।
  6. একটি ব্যর্থ হলে? ধরুন স্টকের অনুরোধ ৪০ ms-এ ব্যর্থ। Promise.all সাথে সাথে সেই error দিয়ে rejected হয়, await-এর লাইনে error ছোড়া হয়। বাকি দুটি অনুরোধ কিন্তু বাতিল হয় না, চলতেই থাকে — শুধু তাদের ফল আর কেউ নেয় না। সত্যিই বাতিল করতে লাগে AbortController (পর্ব ৩৬)। আর “কয়েকটি ব্যর্থ হলেও বাকিগুলো দেখানো” চাইলে Promise.allSettled — নিচে দেখুন।

এই একই ফাঁদ লুপে আরও বড় হয়ে দেখা দেয়। ২০০টি অর্ডারের ডেলিভারি স্ট্যাটাস আনতে হবে:

// ধীর: প্রতিটি অনুরোধ আগেরটি শেষ হওয়ার পর শুরু হয়
const statuses = [];
for (const id of orderIds) {
  statuses.push(await getStatus(id));
}

// দ্রুত: সব একসাথে শুরু, তারপর একবার অপেক্ষা
const statuses2 = await Promise.all(orderIds.map((id) => getStatus(id)));

map প্রতিটি id-এর জন্য function ডাকে আর ফেরত আসা promise-গুলো দিয়ে একটি array বানায় — ডাকার মুহূর্তেই প্রতিটি অনুরোধ চলে যায়। তারপর Promise.all সবগুলোর জন্য একসাথে অপেক্ষা করে।

কখন একটার পর একটাই ঠিক

“সবসময় Promise.all” নিয়মও ভুল। তিনটি ক্ষেত্রে ধারাবাহিক await-ই সঠিক:

  • পরের ধাপ আগের ফলের ওপর নির্ভর করে। চেকআউটে টাকা কাটার আগে স্টক নিশ্চিত হতে হবে; user-এর id না পেলে তার অর্ডার আনা যায় না। এখানে ক্রমটাই যুক্তি।
  • সার্ভারের সীমা আছে (rate limit). SMS gateway যদি সেকেন্ডে ১০টির বেশি অনুরোধ নেয় না, ২০০০টি অনুরোধ একসাথে পাঠালে বেশিরভাগই ফেরত আসবে 429 Too Many Requests দিয়ে। নিজের ডেটাবেসও একসাথে হাজার query-তে হাঁপিয়ে উঠতে পারে।
  • ক্রম নিজেই জরুরি। একই অ্যাকাউন্টে টাকা জমা আর তোলা — ক্রম বদলালে মাঝখানে ব্যালান্স ঋণাত্মক হতে পারে।

মাঝামাঝি পথও আছে: তালিকাকে ১০টি করে ভাগ করুন, প্রতিটি ভাগ Promise.all দিয়ে, ভাগগুলো একটার পর একটা। একসাথে সর্বোচ্চ কয়টি চলবে, সেটা নিয়ন্ত্রণ করা — একে বলে concurrency-র সীমা — বড় প্রজেক্টে p-limit-এর মতো ছোট library দিয়ে করা হয়।

allSettled আর race — await-এর সাথে

dashboard-এ একটি widget ব্যর্থ হলে পুরো পাতা খালি দেখানো খারাপ অভিজ্ঞতা। Promise.allSettled (ES2020) কখনো reject হয় না — প্রতিটি promise-এর ফল { status: "fulfilled", value } বা { status: "rejected", reason } আকারে দেয়:

const results = await Promise.allSettled([getSales(), getStock(), getMsgs()]);
results.forEach((r, i) => {
  if (r.status === "fulfilled") renderWidget(i, r.value);
  else renderWidgetError(i, "এই অংশ এখন লোড করা যায়নি");
});

// সময়সীমা: ৩ সেকেন্ডে উত্তর না এলে হাল ছাড়া
const timeout = (ms) => new Promise((_, reject) =>
  setTimeout(() => reject(new Error(`${ms} ms পেরিয়ে গেছে`)), ms));
const report = await Promise.race([getReport(), timeout(3000)]);

Promise.race যে promise আগে মীমাংসা হয় তার ফল নেয়। রিপোর্ট ৩ সেকেন্ডের মধ্যে এলে রিপোর্ট, না এলে timeout-এর error. তবে মনে রাখুন, race হারলেও getReport()-এর অনুরোধ পেছনে চলতেই থাকে — race শুধু অপেক্ষা থামায়, কাজ নয়। আসলেই বাতিল করার পরিষ্কার উপায় AbortController আর AbortSignal.timeout(), পর্ব ৩৬-এ।

এবার একটি পূর্ণ উদাহরণ চালিয়ে দেখুন — চেকআউট (সফল ও ব্যর্থ), cause দিয়ে মোড়ানো error, finally, আর তিনটি widget একসাথে। এই কোড block-টিতে data-module দেওয়া আছে, তাই এটি একটি module হিসেবে চলে আর একেবারে বাইরে (কোনো async function ছাড়া) await লেখা যায় — একে বলে top-level await, অংশ ৭-এ বিস্তারিত।

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
class PaymentError extends Error { name = "PaymentError"; }
async function charge(amount) {
  await delay(30);                           // ব্যাংকের উত্তরের অপেক্ষা (ভান)
  if (amount > 5000) throw new PaymentError("কার্ডের সীমা পেরিয়েছে");
  return "TXN-881";
}
async function checkout(total) {
  try {
    const txn = await charge(total);
    return `অর্ডার সফল, রসিদ ${txn}`;
  } catch (err) {
    throw new Error("পেমেন্ট হয়নি, টাকা কাটা হয়নি", { cause: err });
  } finally {
    console.log("বাটন আবার চালু");
  }
}
async function widget(name, ms) {
  console.log("শুরু:", name); await delay(ms); console.log("শেষ:", name);
  return name;
}
console.log(await checkout(450));            // top-level await — module বলে চলে
try { await checkout(9000); }
catch (err) { console.log(err.message, "| কারণ:", err.cause.name); }
console.log(await Promise.all([widget("বিক্রি", 60), widget("স্টক", 20), widget("বার্তা", 40)]));

প্রথম চেকআউটে আগে আসে বাটন আবার চালু, তারপর অর্ডার সফল, রসিদ TXN-881 — try-এর return ফেরত মান ঠিক করে, কিন্তু function শেষ হওয়ার আগে finally চলে (পর্ব ২৯)। দ্বিতীয়টিতে ৯০০০ টাকা সীমার বেশি: charge-এর promise ব্যর্থ হলো, await-এর লাইনে সেটা throw হয়ে ধরা পড়ল catch-এ, catch নতুন error ছুড়ল আসলটিকে cause-এ রেখে। আউটপুট: বাটন আবার চালু, তারপর পেমেন্ট হয়নি, টাকা কাটা হয়নি | কারণ: PaymentError. শেষ অংশে তিনটি শুরু: আগে একসাথে — তিনটি কাজই একই মুহূর্তে শুরু হয়েছে। শেষ হয় স্টক (২০), বার্তা (৪০), বিক্রি (৬০) ক্রমে, কিন্তু ফলের array [ 'বিক্রি', 'স্টক', 'বার্তা' ] — দেওয়া ক্রমে। মোট অপেক্ষা ৬০ ms, ১২০ নয়।

৬ · forEach অপেক্ষা করে না — আর for await…of

অর্ডারগুলো একটি একটি করে সেভ করতে হবে। অনেকে প্রথমে লেখেন orders.forEach(async (id) => { await save(id); }), তারপর লেখেন “সব শেষ”. দেখতে নির্দোষ, কিন্তু এটি async/await-এর সবচেয়ে পরিচিত বাগগুলোর একটি। ধাপে ধাপে দেখুন (ধরা যাক save(101) নেয় ৩০ ms, save(102) নেয় ১০ ms):

1// ক) forEach2orders.forEach(async (id) => {3  await save(id);4  console.log("সেভ:", id);5});6console.log("সব শেষ");7// খ) for…of (async function-এর ভেতরে)8for (const id of orders) {9  await save(id);10  console.log("সেভ:", id);11}12console.log("সব শেষ");
এই মুহূর্তে
ঘড়ি
চলমান save
কেউ অপেক্ষা করছে?
  1. forEach প্রথম callback ডাকল। callback async, তাই save(101) শুরু করে প্রথম await-এ থামল আর একটি promise ফেরত দিল। কিন্তু forEach ফেরত মান দেখে না — সে তৈরিই হয়েছে সাধারণ, synchronous callback-এর জন্য। promise-টি সাথে সাথে হারিয়ে গেল।
  2. অপেক্ষা ছাড়াই পরের callback. forEach থামেনি, সাথে সাথে 102-এর callback ডাকল — সেটিও await-এ থেমে promise দিল, সেটিও হারাল। দুটি save এখন একসাথে চলছে, যদিও আপনি চেয়েছিলেন একটি একটি করে।
  3. “সব শেষ” — অথচ কিছুই শেষ হয়নি। forEach ফিরে এল undefined নিয়ে, লাইন ৬ ছাপল সব শেষ. এখানে যদি “সফল” বার্তা দেখাতেন বা পাতা বন্ধ করতেন, তা ঘটত সেভ হওয়ার আগেই। forEach-এর সামনে await লিখলেও কিছু বদলাত না — await undefined.
  4. পরে, যে আগে পারে। ১০ ms-এ সেভ: 102, ৩০ ms-এ সেভ: 101 — ক্রমও উল্টে গেল। আর কোনো save ব্যর্থ হলে? সেই promise-এর কোনো অভিভাবক নেই — একটি unhandled rejection, বাইরের কোনো try/catch তা ধরতে পারে না।
  5. for…of: await লুপকেই থামায়। এখানে await আছে লুপের শরীরে, আর লুপ নিজেই async function-এর অংশ। save(101)-এ পুরো function থামল — পরের পুনরাবৃত্তি শুরুই হয়নি। ৩০ ms-এ সেভ: 101, তারপর লুপ এগোল, save(102) শুরু।
  6. ক্রম ঠিক, “সব শেষ” সত্যিই শেষে। ৪০ ms-এ সেভ: 102, তারপর লুপ শেষ, তারপর সব শেষ. আর কোনো save ব্যর্থ হলে error ঠিক এই function-এ ছোড়া হতো, চারপাশের try/catch ধরত। একসাথে চালাতে চাইলে await Promise.all(orders.map(save)) — দ্রুত, তবু “সব শেষ” আসে সত্যিই সব শেষে।

একই সমস্যা আছে অন্য array method-এও: map একটি async callback পেলে মানের বদলে promise-এর array দেয় (তাই Promise.all দিয়ে মুড়তে হয়); filter-এর async predicate সবসময় একটি promise ফেরত দেয়, যা truthy — তাই কিছুই বাদ পড়ে না; reduce-এ accumulator হয়ে যায় promise. নিয়ম: array method-এ async callback দিলে ফেরত আসা promise-গুলো কে নিচ্ছে, সেটা জিজ্ঞেস করুন।

for await…of — যখন ডেটা নিজেই আসে ধীরে ধীরে

অনেক API সব ডেটা একবারে দেয় না, দেয় পাতায় পাতায় — “প্রথম ২০টি অর্ডার, আর পরের পাতার চিহ্ন”. পর্ব ২৭-এ দেখেছেন async generator (async function*) — যার ভেতরে await আর yield দুটোই চলে। সেটি দিয়ে পাতাগুলোকে একটি সাধারণ তালিকার মতো সাজানো যায়, আর for await…of (ES2018) প্রতিটি মানের জন্য অপেক্ষা করে একে একে দেয়:

// নকল API: প্রতিটি পাতায় কয়েকটি অর্ডার আর পরের পাতার নম্বর
async function fetchPage(page) {
  await delay(50);
  const pages = { 1: [[101, 102], 2], 2: [[103], 3], 3: [[104, 105], null] };
  const [items, next] = pages[page];
  return { items, next };
}

async function* allOrders() {
  let page = 1;
  while (page !== null) {
    const { items, next } = await fetchPage(page);   // পাতা আনা
    yield* items;                                      // এক এক করে দেওয়া
    page = next;
  }
}

for await (const id of allOrders()) {
  console.log("অর্ডার", id);          // 101, 102, 103 — তারপর break
  if (id === 103) break;               // break করলে পরের পাতা আর আনাই হয় না
}

সুবিধা দুটি। ব্যবহারকারীর কোড জানে না পাতা কয়টি — তার কাছে এটা শুধু একটি তালিকা। আর ডেটা আনা হয় শুধু যতটুকু লাগে: break করলে for await async iterator-এর return() ডাকে, generator থেমে যায়, পাতা ৩ আর কখনো চাওয়া হয় না। Node.js-এ ফাইলের লাইন পড়া আর stream-ও (পর্ব ৪৯) এভাবে পড়া যায়। মনে রাখুন: for await নিজেও await — এটি একটার পর একটা চলে, তাই স্বাধীন জিনিসের সাধারণ array-এর জন্য এটি দ্রুত পথ নয়।

৭ · আরও কিছু নিয়ম — return await, fire-and-forget, factory আর top-level await

return নাকি return await?

async function থেকে একটি promise ফেরত দেওয়ার সময় return p আর return await p — বেশিরভাগ ক্ষেত্রে একই ফল। কিন্তু try-এর ভেতরে পার্থক্যটা বড়:

ক) return — ধাপ ১promise ফেরত, try শেষ
async function a() {
  try {
    return failLater();     // ব্যর্থ হবে ১০ ms পরে
  } catch {
    return "a ধরল";
  }
}
→ promise-টি সাথে সাথে ফেরত গেল, try block এখানেই শেষ
ক) return — ধাপ ২catch আর পাহারায় নেই
a().then(v => console.log("a:", v),
         e => console.log("a rejected:", e.message));
→ a rejected: ব্যর্থ
// ১০ ms পরে promise ব্যর্থ হলো, কিন্তু a()-এর try অনেক আগেই শেষ।
// catch কখনো চলেনি — error সোজা ডাকার জায়গায়।
খ) return await — ধাপ ১try-এর ভেতরেই অপেক্ষা
async function b() {
  try {
    return await failLater();   // এখানে থামে, try সক্রিয় থাকে
  } catch {
    return "b ধরল";
  }
}
→ function থেমে আছে try block-এর ভেতরে
খ) return await — ধাপ ২catch ধরল
async function b() {
  try {
    return await failLater();   // ← এখানে throw হলো
  } catch {
    return "b ধরল";
  }
}
→ b: b ধরল
  1. return failLater() অপেক্ষা করে না। failLater() একটি অপেক্ষমাণ promise দেয়, আর return সেটাকে সাথে সাথে ফেরত মান বানিয়ে function থেকে বের হয়ে যায়। try block-এর কাজ এখানেই শেষ — এই মুহূর্তে কোনো error নেই, তাই catch-এর কিছু করার নেই।
  2. ব্যর্থতা আসে পরে, catch-এর অনুপস্থিতিতে। ১০ ms পরে promise ব্যর্থ হয়। a()-এর ফেরত promise সেটাকেই অনুসরণ করে, তাই ডাকার কোড পায় a rejected: ব্যর্থ. লেখা catch-টি মৃত কোড — কখনো চলবে না। Node.js-এ চালিয়ে যাচাই করা।
  3. return await try-এর ভেতরেই থামে। এবার await function-টিকে try block-এর ভেতরে থামিয়ে রাখে। promise মীমাংসা না হওয়া পর্যন্ত function বের হয় না, তাই try তখনো সক্রিয়।
  4. ব্যর্থতা await-এর লাইনে throw হয়ে catch-এ। promise ব্যর্থ হতেই await-এর জায়গায় error ছোড়া হলো, catch ধরল, ফেরত গেল "b ধরল". আরও একটি লাভ: stack trace-এ b থাকে, কারণ ব্যর্থতার সময় b তখনো চালু ছিল (অংশ ৮)। try-এর বাইরে return await ক্ষতিকর নয়, তবে দরকারও নেই; typescript-eslint-এর return-await নিয়মের ডিফল্ট ঠিক এটাই চায় — try-এর ভেতরে await আবশ্যক।

Fire-and-forget — যে promise-এর কোনো অভিভাবক নেই

await বা .catch ছাড়া কোনো async function ডেকে চলে যাওয়াকে বলে fire-and-forget. কখনো এটা ইচ্ছাকৃত (রসিদের SMS, analytics) — তখনো একটি .catch লাগবেই। কিন্তু বেশিরভাগ সময় এটা দুর্ঘটনা: await লিখতে ভুলে যাওয়া। ফল তিন রকম হতে পারে — (১) কাজ শেষ হওয়ার আগেই পরের ধাপ চলে (“সেভ হয়েছে” দেখাল, অথচ হয়নি); (২) ব্যর্থ হলে unhandled rejection — ব্রাউজারে console-এ Uncaught (in promise), Node.js 15 থেকে ডিফল্টে পুরো process বন্ধ; (৩) শর্তে promise ঢুকে যাওয়া:

async function isCouponValid(code) { /* সার্ভারে যাচাই ... */ return false; }

if (isCouponValid("EID50")) {       // ভুল! promise একটি object — সবসময় truthy
  applyDiscount();                  // প্রতিটি কুপনে ছাড় পেয়ে যাবে সবাই
}
if (await isCouponValid("EID50")) { /* ঠিক */ }

Node.js-এ console.log(isCouponValid("EID50")) ছাপে Promise { false } — দেখলেই বোঝা যায় কিছু একটা ভুল। TypeScript-ঘেঁষা টুল এটা ধরতে পারে: typescript-eslint-এর no-floating-promises (অভিভাবকহীন promise) আর no-misused-promises (শর্তে promise) নিয়ম।

async constructor হয় না — static factory

একটি Database class, যার তৈরি হওয়ার সময় সংযোগ খুলতে হবে (async কাজ)। constructor async হতে পারে না, আর constructor-এর ভেতরে await ছাড়া promise চালালে অর্ধেক-প্রস্তুত object বেরিয়ে আসে। পরিচিত সমাধান static factory:

class Database {
  #conn;
  constructor(conn) { this.#conn = conn; }       // শুধু সাজানো, কোনো async কাজ নয়
  static async connect(url) {
    const conn = await openConnection(url);       // async প্রস্তুতি এখানে
    return new Database(conn);                    // সম্পূর্ণ প্রস্তুত object
  }
  async query(sql) { return this.#conn.run(sql); }
}
const db = await Database.connect("postgres://...");   // ফলে সবসময় প্রস্তুত db

Top-level await — শুধু module-এ

ES2022 থেকে একটি module-এর একেবারে বাইরের স্তরে, কোনো async function ছাড়াই, await লেখা যায় — একে বলে top-level await. config বা অনুবাদ-ফাইল লোড করে তারপর বাকি কোড চালানোর জন্য এটা খুব কাজের: const config = await loadConfig();. ব্রাউজারে <script type="module"> লাগে, Node.js-এ .mjs ফাইল বা "type": "module" (পর্ব ৪৫)। সাধারণ (classic) script-এ লিখলে SyntaxError. এই পাতার চালানো যায় এমন কোডে data-module attribute থাকলে runner সেটাকে module হিসেবে চালায় — অংশ ৫-এর উদাহরণটি তাই।

একটি দিক খেয়াল রাখার মতো: top-level await চলাকালীন যে module-গুলো এই module-কে import করে, তারা অপেক্ষা করে। তাই module-এর শুরুতে ধীর নেটওয়ার্ক অনুরোধ await করলে পুরো অ্যাপের শুরু ধীর হতে পারে। এই আশঙ্কা নিয়ে বিতর্কের গল্প ভাবনার প্রশ্ন ৪-এ।

৮ · async কোড ডিবাগ করা — stack trace আর DevTools

পর্ব ৩১-এ দেখেছেন, await-এর পরে function আবার চলে একটি খালি call stack-এ, microtask হিসেবে। তাহলে await-এর পরে error হলে stack trace-এ কে ডেকেছিল, তা দেখা যাবে কীভাবে? আগে দেখা যেত না — শুধু ভুলের লাইন, আর নিচে কিছু engine-এর ভেতরের নাম। আধুনিক V8 (Edge, Chrome, Node.js) await-এর শিকল ধরে পেছনের ডাকগুলো আবার সাজিয়ে দেয়, at async চিহ্ন দিয়ে — একে বলে async stack trace:

PaymentError: কার্ডের সীমা পেরিয়েছে
    at charge (checkout.js:5:29)
    at async checkout (checkout.js:10:17)
    at async onCheckoutClick (app.js:22:5)

শর্ত একটাই: শিকলের প্রতিটি ধাপে await থাকতে হবে। .then দিয়ে লেখা শিকল বা await ছাড়া ফেরত দেওয়া promise-এ (অংশ ৭-এর return p) ওই function stack-এ বাদ পড়ে যেতে পারে। কারণটা ভাবনার প্রশ্ন ৫-এ।

  • Breakpoint আর step: DevTools-এর Sources প্যানেলে await-ওয়ালা লাইনে breakpoint দিন। “Step over” (F10) চাপলে debugger await পেরিয়ে পরের লাইনে থামে — মাঝে অন্য কাজ চললেও। Call Stack প্যানেলে async অংশ আলাদা করে দেখায়।
  • Pause on exceptions: “Pause on caught exceptions” চালু করলে catch-এ ধরা পড়া ব্যর্থ promise-এর জায়গাতেও থামে — কোথায় কোন error চাপা পড়ছে, দেখার সেরা উপায় (পর্ব ২৯)।
  • log-এ promise দেখলে সন্দেহ করুন: কোনো মানের জায়গায় Promise {<pending>} দেখা মানে প্রায় নিশ্চিতভাবে কোথাও await বাদ পড়েছে।
  • সময় মাপুন, অনুমান নয়: Network প্যানেলের waterfall-এ অনুরোধগুলো সিঁড়ির মতো একটির পর একটি সাজানো থাকলে, সম্ভবত অংশ ৫-এর ফাঁদ — স্বাধীন অনুরোধ হলে সেগুলো পাশাপাশি থাকার কথা।

C/C++/Python/Java জানলে: Python-এর async def/await প্রায় হুবহু এক — শুধু Python-এ coroutine ডাকলে কিছুই চলে না যতক্ষণ না await বা asyncio.run করেন, আর JavaScript-এ async function ডাকামাত্র প্রথম await পর্যন্ত চলে (Python · asyncio). C++20-এর co_await একই “থেমে আবার চলা” ধারণা, কিন্তু event loop বা promise টাইপ নিজে বা library দিয়ে জোগাতে হয় (C++20/23 · coroutines). Java ভিন্ন পথ নিয়েছে: Java 21-এর virtual thread-এ সাধারণ blocking কোডই সস্তায় হাজারো thread-এ চলে, তাই আলাদা async/await syntax লাগে না (Java · virtual threads). C-তে ভাষার ভেতরে এমন কিছু নেই — callback বা event library-ই ভরসা।

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

AI-এর লেখা async কোড প্রায় সবসময় চলে, আর পরীক্ষার ছোট ডেটায় ঠিক ফলও দেয়। সমস্যা ধরা পড়ে পরে: ২০০টি অর্ডারে পাতা ২০ সেকেন্ড ঝুলে থাকে, “সেভ হয়েছে” দেখায় অথচ ডেটাবেসে কিছু নেই, বা ছাড়ের কুপন সবার জন্য খুলে যায়। এগুলো syntax-এর ভুল নয়, সময়ের ভুল — শুধু কোড পড়ে ধরতে হয়।

AI যা প্রায়ই লেখেসমস্যাযা চাইবেন
স্বাধীন অনুরোধের জন্য লুপে awaitঅকারণে একটার পর একটা — n গুণ ধীরPromise.all(items.map(...)), প্রয়োজনে সীমা রেখে
items.forEach(async (x) => …)কেউ অপেক্ষা করে না, error অভিভাবকহীনfor…of (ক্রমে) বা Promise.all (একসাথে)
প্রতিটি await-এর চারপাশে আলাদা try/catch, ভেতরে শুধু console.logভুল গিলে ফেলে পরের ধাপ চলে undefined নিয়েএকটি try পুরো কাজের জন্য; আলাদা catch শুধু যেখানে সত্যিকারের fallback আছে
await ভুলে যাওয়া — if (isValid()), const user = getUser()promise সবসময় truthy; log-এ Promise {<pending>}প্রতিটি async ডাকে await; no-floating-promises নিয়ম
await বা .catch ছাড়া ডেকে চলে যাওয়াunhandled rejection; Node.js-এ process বন্ধawait, নয়তো ইচ্ছাকৃত হলে .catch(logError)
try-এর ভেতরে return promisecatch কখনো চলে নাtry-এর ভেতরে return await

AI-কে async কোড লিখতে বললে কোন কাজ কার ওপর নির্ভর করে, সেটা আগেই বলে দিন — AI নিজে থেকে সেটা জানে না:

আমি JavaScript-এ একটি অনলাইন দোকানের loadDashboard() আর processOrders(ids) ফাংশন লিখছি। নিচের নিয়ম মেনে async/await দিয়ে কোড লিখে দাও: ১. getSales(), getStock(), getMessages() একে অপরের ওপর নির্ভর করে না — এগুলো Promise.allSettled দিয়ে একসাথে চালাবে, আর যেটি ব্যর্থ হবে শুধু সেই widget-এ বাংলায় বার্তা দেখাবে। ২. processOrders-এ প্রতিটি অর্ডারের জন্য চার্জ করা ক্রমে হবে না, কিন্তু একসাথে ৫টির বেশি অনুরোধ যাবে না (gateway-এর সীমা) — কীভাবে সীমা রাখলে সেটা ব্যাখ্যা করবে। ৩. কোথাও forEach-এ async callback থাকবে না; await ছাড়া কোনো promise ফেলে রাখবে না — ইচ্ছাকৃত হলে .catch দেবে। ৪. প্রতিটি await-এর চারপাশে try/catch নয়; একটি try পুরো কাজের জন্য, error মুড়বে { cause: err } দিয়ে; try-এর ভেতরে return করলে return await. ৫. ব্যবহারকারীকে শুধু বোধগম্য বাংলা বার্তা, বিস্তারিত logError()-এ। শেষে বলো: কোন await-গুলো একটার পর একটা আর কেন, আর মোট সময় মোটামুটি কত হবে যদি প্রতিটি অনুরোধ ২০০ ms নেয়।

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

পরীক্ষা ও ইন্টারভিউতে:

  • async function কী ফেরত দেয়? — সবসময় একটি Promise; return-এর মান দিয়ে পূর্ণ, throw হলে rejected.
  • await কি পুরো প্রোগ্রাম থামায়? — না, শুধু ওই async function-কে; বাকি কোড ও event loop চলতে থাকে, পরের অংশ microtask হিসেবে আবার চলে।
  • লুপে await আর Promise.all-এর পার্থক্য? — লুপে await একটার পর একটা (মোট সময় যোগফল), Promise.all একসাথে (মোট সময় সবচেয়ে ধীরটির সমান), কিন্তু একটি ব্যর্থ হলেই reject.
  • forEach-এ async callback দিলে কী হয়? — forEach promise-গুলোর জন্য অপেক্ষা করে না; পরের লাইন আগেই চলে, error অভিভাবকহীন থাকে।
  • top-level await কোথায় চলে? — শুধু ES module-এ (ES2022); classic script-এ SyntaxError.

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

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

প্র ০১async/await প্রথম মূলধারায় এলো C# 5-এ (২০১২)। তারপর Python, JavaScript, Rust, Swift — প্রায় সবাই একই নকশা নিল কেন?

২০০০-এর দশকের শেষে Microsoft একটি পরিচিত সমস্যায় ভুগছিল। Windows অ্যাপের UI চলে একটি thread-এ। সেই thread-এ ফাইল বা নেটওয়ার্কের জন্য অপেক্ষা করলে পুরো জানালা জমে যায় — “Not Responding”. সমাধান ছিল callback-ভিত্তিক asynchronous API, কিন্তু সেগুলো দিয়ে লুপ, try/catch বা সাধারণ if-else লেখা ছিল কষ্টকর। প্রোগ্রামাররা তাই সহজ কিন্তু জমে যাওয়া synchronous কোডই লিখতেন।

এর আগেই F#-এ Don Syme-এর দল ২০০৭ সালের দিকে “async workflows” এনেছিল, যেখানে let! দিয়ে অপেক্ষা লেখা যেত। Anders Hejlsberg-এর নেতৃত্বে C#-এর দল এই ধারণাকে একটি মূলধারার, imperative ভাষায় আনলেন: async চিহ্নিত method আর await keyword. compiler পুরো method-কে একটি state machine-এ রূপান্তর করে — প্রতিটি await একটি “থামার বিন্দু”, local variable-গুলো সেই state machine-এর field. প্রোগ্রামার লেখেন সাধারণ কোড, চলে callback-ভিত্তিক কোড। C# 5 আর .NET 4.5 ২০১২ সালে এটা নিয়ে আসে।

ধারণাটি দ্রুত ছড়াল, কারণ সমস্যাটি সর্বজনীন ছিল — প্রতিটি ভাষাতেই I/O-র অপেক্ষা আর callback-এর জট। Python 3.5 (২০১৫) PEP 492-এর মাধ্যমে async def/await আনে (Yury Selivanov-এর প্রস্তাব), আগের generator-ভিত্তিক yield from কৌশলের জায়গায়। JavaScript পায় ES2017-এ। Rust ২০১৯ সালে (সংস্করণ ১.৩৯) async/await স্থিতিশীল করে, কিন্তু নিজের ধাঁচে — Rust-এর future “অলস”, poll না করলে কিছুই চলে না, আর runtime ভাষার অংশ নয়, Tokio-র মতো library জোগায়। Swift 5.5 (২০২১) async/await আনে structured concurrency-র সাথে। Kotlin একই সমস্যা সমাধান করে suspend function দিয়ে, প্রায় একই ধারণা ভিন্ন শব্দে।

লক্ষণীয় হলো, প্রতিটি ভাষা চেহারা নিয়েছে C# থেকে, কিন্তু ভেতরের যন্ত্র নিজের। JavaScript-এ নিচে promise আর microtask; Python-এ coroutine object আর asyncio-র event loop; Rust-এ Future trait আর executor. একই syntax, আলাদা অর্থের খুঁটিনাটি — যেমন Python-এ coroutine ডাকলে কিছুই চলে না, JavaScript-এ প্রথম await পর্যন্ত চলে যায়। এক ভাষা থেকে আরেক ভাষায় গেলে এই পার্থক্যগুলোই বাগের উৎস।

মূল উপলব্ধি: async/await সফল হয়েছে কারণ এটা প্রোগ্রামারকে নতুন কিছু শেখায়নি — পুরোনো, চেনা কাঠামো (ক্রমিক লাইন, লুপ, try/catch) ফিরিয়ে দিয়েছে, আর জটিলতাটুকু compiler-এর ঘাড়ে চাপিয়েছে।

প্র ০২“generator + promise = async/await” — ES2017-এর আগে লোকে co, regenerator আর Babel দিয়ে কীভাবে async/await লিখত? এই রূপান্তর (desugaring) থেকে async function-এর আসল চেহারা কী বোঝা যায়?

ES2015-এ JavaScript দুটি জিনিস পেল: promise আর generator (পর্ব ২৭)। generator-এর বিশেষ ক্ষমতা হলো yield-এ থেমে যাওয়া আর বাইরে থেকে next(value) দিয়ে আবার চালানো — আর সেই value হয়ে যায় yield expression-এর ফল। কেউ যদি generator থেকে একটি promise yield করে, আর বাইরের একটি “চালক” function সেই promise পূর্ণ হলে next(ফল) ডাকে, ব্যর্থ হলে throw(error) ডাকে — তাহলে generator-এর ভেতরের কোড দেখতে ঠিক async/await-এর মতো হয়: const user = yield getUser();

এই চালকটিই TJ Holowaychuk-এর জনপ্রিয় co library (২০১৩)। Node.js-এর Koa framework-এর প্রথম সংস্করণ পুরোটাই এর ওপর দাঁড়ানো ছিল। co(function* () { const a = yield fetchA(); … }) — শুধু yield-এর জায়গায় await আর function*-এর জায়গায় async function বসান, পেয়ে যাবেন আজকের কোড। চালকের মূল যুক্তি মাত্র কয়েক লাইন: generator-এর next ডাকা; ফল যদি done হয় তো শেষ, নাহলে yield করা মানটিকে promise বানিয়ে then-এ নিজেকে আবার ডাকা।

কিন্তু পুরোনো ব্রাউজারে তো generator-ও ছিল না। সেখানে আসে Facebook-এর regenerator (Ben Newman, ২০১৩–১৪) — এটি generator function-কে একটি সাধারণ function-এ রূপান্তর করত, যার ভেতরে একটি switch-ভিত্তিক state machine: প্রতিটি yield একটি case, local variable-গুলো একটি context object-এ। Babel এই দুটি ধাপ জুড়ে দিত — async function → generator + চালক → regenerator-এর state machine. ফলে ২০১৫-১৬ সালেই লাখো ডেভেলপার async/await লিখছিলেন, যা আসলে চলত পুরোনো ES5-এ।

এই রূপান্তর async function-এর আসল চেহারা দেখায়। এটি একটি থামতে পারা function (coroutine) আর একটি চালক — যেটি প্রতিটি থামার বিন্দুতে promise-এর then-এ বাকি অংশ জুড়ে দেয়। এজন্যই await-এর পরের কোড microtask-এ চলে (then-এর callback), এজন্যই error await-এর লাইনে throw হয় (চালকের gen.throw), আর এজন্যই async function promise ফেরত দেয় (চালক নিজেই একটি promise বানায়)। মানদণ্ডে এসে engine এই কাজ নিজে, অনেক দক্ষভাবে করে; কিন্তু ধারণাগত মডেল বদলায়নি।

মূল উপলব্ধি: নতুন syntax প্রায়ই পুরোনো, প্রমাণিত কৌশলের নামকরণ। async/await বুঝতে generator আর promise বুঝুন — আর মনে রাখুন, ভাষার বৈশিষ্ট্য অনেক সময় মানদণ্ডে আসার আগেই community-র library-তে পরীক্ষিত হয়।

প্র ০৩Bob Nystrom-এর “What Color is Your Function?” (২০১৫) বলে async function একটি আলাদা “রঙের” function. এই function-coloring সমস্যা কী, আর Go বা Java-র virtual thread কীভাবে এটা এড়ায়?

২০১৫ সালে Google-এর Dart দলের Bob Nystrom একটি প্রবন্ধ লেখেন, যা আজও async নিয়ে আলোচনায় সবচেয়ে বেশি উদ্ধৃত লেখাগুলোর একটি। তিনি একটি কাল্পনিক ভাষা কল্পনা করেন, যেখানে প্রতিটি function হয় “লাল” নয় “নীল”. নিয়ম: লাল function শুধু অন্য লাল function-এর ভেতর থেকে ডাকা যায়; নীল function যেকোনো জায়গা থেকে। লাল function ডাকা কষ্টকর। আর কিছু দরকারি library function শুধু লাল।

রূপকটি স্পষ্ট: লাল = async. JavaScript-এ async function-এর ফল পেতে await লাগে, await লিখতে নিজের function-কেও async হতে হয়। তাই কোনো গভীরের একটি ছোট function-কে async বানালে (ধরুন, এখন সেটা ডেটাবেস থেকে একটি মান পড়ে), তার সব caller, তাদের caller… একেবারে ওপর পর্যন্ত async হয়ে যায়। রঙ ছড়িয়ে পড়ে। Array.prototype.map-এর মতো “নীল” higher-order function লাল callback ঠিকমতো সামলাতে পারে না — অংশ ৬-এর forEach-এর বাগ ঠিক এটাই। একই library-র sync আর async দুই সংস্করণ রাখতে হয় (Node.js-এর fs.readFileSync আর fs.promises.readFile).

Nystrom-এর যুক্তি ছিল, সমস্যার মূল হলো ভাষা thread বা continuation-কে প্রথম শ্রেণির সুবিধা হিসেবে দেয় না, তাই থামার ক্ষমতাটা function-এর টাইপে লিখে রাখতে হয়। তিনি Go-র উদাহরণ দেন: Go-তে সব function এক রঙের। goroutine খুব সস্তা, আর একটি goroutine নেটওয়ার্কের জন্য “blocking” অপেক্ষা করলে runtime নিজেই তাকে সরিয়ে অন্য goroutine চালায়। কোড synchronous দেখায়, আচরণ asynchronous. Java 21-এর virtual thread (Project Loom) একই পথ নিয়েছে — Java সচেতনভাবে async/await যোগ করেনি; পুরোনো blocking কোডই হাজারো virtual thread-এ সস্তায় চলে।

তাহলে JavaScript কেন সেই পথে যায় না? কারণ JavaScript-এর একটি মৌলিক নিশ্চয়তা আছে — “run-to-completion”: একটি synchronous কোডের টুকরো চলাকালীন অন্য কেউ মাঝখানে ঢুকে আপনার ডেটা বদলাতে পারবে না। যেকোনো function যেকোনো সময় থামতে পারলে এই নিশ্চয়তা ভেঙে যেত, আর লক ছাড়া shared state নিরাপদ থাকত না। রঙ তাই একটি সতর্কসংকেতও — await দেখলেই আপনি জানেন, “এখানে অন্য কোড ঢুকতে পারে”. অনেকে একে খরচ নয়, তথ্য হিসেবে দেখেন।

মূল উপলব্ধি: function coloring হলো একটি বিনিময় — থামার জায়গাগুলো কোডে দৃশ্যমান রাখার বিনিময়ে রঙ ছড়ানোর বিরক্তি। Go/Java সেই দৃশ্যমানতা ছেড়ে সরলতা নিয়েছে, JavaScript নিয়েছে উল্টোটা।

প্র ০৪top-level await নিয়ে বিতর্ক কেন হয়েছিল? Rich Harris-এর আপত্তি কী ছিল, আর ES2022-এর চূড়ান্ত নকশা সেগুলোর উত্তর কীভাবে দিল?

async/await আসার পর একটি বিরক্তিকর নিয়ম থেকে গেল: await শুধু async function-এর ভেতরে। একটি module-এর শুরুতে config লোড করতে চাইলে পুরো কোড একটি (async () => { … })()-এ মুড়তে হতো, আর export করা মান পাওয়া যেত শুধু পরে। স্বাভাবিক প্রস্তাব এলো: module-এর বাইরের স্তরেই await চলুক। TC39-এ প্রস্তাবটি এগিয়ে নেন Myles Borins, Yulia Startsev আর Guy Bedford.

কিন্তু সবাই খুশি ছিলেন না। Svelte আর Rollup-এর স্রষ্টা Rich Harris প্রস্তাবের শুরুর দিকে একটি আলোচিত gist লেখেন, শিরোনাম মোটামুটি “Top-level await is a footgun” — নিজের পায়ে গুলি করার অস্ত্র। তাঁর মূল আশঙ্কাগুলো: (১) কোনো module await করলে তাকে import করা সব module অপেক্ষা করবে — একটি গভীর dependency-র ধীর নেটওয়ার্ক অনুরোধ নিঃশব্দে পুরো অ্যাপের শুরু আটকে দেবে, আর কেউ বুঝবেও না কোথায়। (২) দুটি module একে অপরকে import করলে (circular dependency) আর দুটিতেই await থাকলে deadlock হতে পারে। (৩) bundler-এর পক্ষে module-গুলো একসাথে জুড়ে দেওয়া কঠিন হবে।

আলোচনায় নকশাটি বদলায়। প্রথম দিকের একটি ধারণা ছিল পুরো module graph-কে আটকে দেওয়া; চূড়ান্ত নকশায় শুধু সেই module-গুলো অপেক্ষা করে, যারা await-ওয়ালা module-টির ওপর নির্ভর করে। পাশাপাশি থাকা অন্য শাখার module আগের মতোই সমান্তরালে লোড ও চালু হয়। অর্থাৎ top-level await একটি module-কে নিজেই একটি async function-এর মতো বানিয়ে দেয়, আর import করা মানে “তার শেষ হওয়ার অপেক্ষা”. circular dependency-র ঝুঁকি পুরোপুরি যায়নি, তবে নির্দিষ্ট ও ব্যাখ্যাযোগ্য আচরণ পেয়েছে। প্রস্তাবটি ২০১৯ সালে stage 3-এ ওঠে আর ২০২১ সালে চূড়ান্ত হয়ে ES2022-এ যুক্ত হয়; V8-এ Chrome 89 থেকে আর Node.js 14.8 থেকে কোনো flag ছাড়াই চলে।

ব্যবহারিক শিক্ষা আজও Harris-এর সতর্কবার্তার কাছাকাছি: top-level await ব্যবহার করুন অ্যাপের একেবারে প্রবেশপথে (entry module) বা সত্যিই আবশ্যক প্রস্তুতিতে — যেমন WebAssembly module লোড, config পড়া, বা dynamic import() দিয়ে শর্তসাপেক্ষে library বেছে নেওয়া। একটি পুনর্ব্যবহারযোগ্য library-র গভীরে ধীর নেটওয়ার্ক অনুরোধ await করা এখনো খারাপ অভ্যাস।

মূল উপলব্ধি: ভাষার নকশায় তীক্ষ্ণ সমালোচনা ক্ষতি নয়, উপকার। একটি gist-এর আপত্তি প্রস্তাবটিকে আটকায়নি, বরং আরও সংকীর্ণ ও নিরাপদ করেছে — TC39-এর প্রক্রিয়া ঠিক এভাবেই কাজ করার কথা।

প্র ০৫await-এর পরে call stack খালি থাকে, তবু আধুনিক V8 stack trace-এ at async … দেখায় কীভাবে? “zero-cost async stack trace” কী, আর এটা কেন শুধু await-এ কাজ করে, then-এ নয়?

সমস্যাটা মৌলিক। একটি async function await-এ থামলে তার frame call stack থেকে নেমে যায়। পরে promise পূর্ণ হলে function আবার চলে একটি microtask থেকে — একদম খালি stack-এ। তখন error ছোড়া হলে engine stack-এ যা দেখে, তা শুধু এই function আর microtask-এর যন্ত্রপাতি; কে একে প্রথমে ডেকেছিল, সেই তথ্য stack-এ আর নেই। ফলে পুরোনো দিনে async কোডের stack trace প্রায়ই ছিল দুই লাইনের, অর্থহীন।

DevTools অনেক আগে থেকেই একটি সমাধান দিত: “async stack traces” — প্রতিটি promise তৈরি বা callback নিবন্ধনের মুহূর্তে পুরো stack রেকর্ড করে রাখা, যাতে পরে জোড়া দেওয়া যায়। কিন্তু প্রতিটি await-এ পুরো stack রেকর্ড করা ব্যয়বহুল, তাই এটা চলত শুধু DevTools খোলা থাকলে। production-এ, যেখানে সত্যিকারের ক্র্যাশ ঘটে, কোনো তথ্য নেই।

২০১৮ সালে V8 দল (Benedikt Meurer, Maya Lekova প্রমুখ) একটি চতুর পর্যবেক্ষণ করে। await-এ থেমে থাকা একটি async function জানে সে কোন promise-এর জন্য অপেক্ষা করছে, আর তার নিজের ফেরত promise-এর ওপর কে await করছে, সেটাও ওই promise-এর reaction তালিকায় লেখা থাকে। অর্থাৎ “কে কার জন্য অপেক্ষা করছে” — এই শিকল heap-এ এমনিতেই আছে! error-এর মুহূর্তে engine সেই শিকল ধরে হেঁটে গেলেই পুরো async ডাকের ক্রম পাওয়া যায়, আগে থেকে কিছু রেকর্ড না করেই। স্বাভাবিক চলার সময় কোনো বাড়তি খরচ নেই — তাই “zero-cost”. V8 7.3 (২০১৯-এর শুরু) থেকে এটি ডিফল্টে চালু, আর Node.js 12 থেকে Node-এর stack trace-এ at async লাইন দেখা যায়।

কিন্তু এই কৌশল নির্ভর করে await-এর শিকলের ওপর। .then(cb)-এর ক্ষেত্রে callback-টি কোন function থেকে নিবন্ধিত হয়েছিল, সেই function হয়তো অনেক আগেই শেষ — তার কোনো “অপেক্ষমাণ অবস্থা” heap-এ নেই, জুড়বার সুতো নেই। একই কারণে return p (await ছাড়া) করলে ওই function শিকল থেকে বাদ পড়ে। এই কাজের সময় V8 দল একটি মানদণ্ড-পরিবর্তনও প্রস্তাব করে, যাতে প্রতিটি await-এ বাড়তি promise আর microtask তৈরি কম হয় — ফলে await দ্রুততর হয়; পরিবর্তনটি পরে মানদণ্ডেও গৃহীত হয়। দুটি মিলিয়ে বার্তাটা পরিষ্কার: নতুন কোডে then-শিকলের চেয়ে await শুধু পড়তে সহজ নয়, debugging আর গতিতেও এগিয়ে।

মূল উপলব্ধি: ভালো engineering প্রায়ই নতুন তথ্য জমানো নয়, ইতিমধ্যে থাকা তথ্য খুঁজে পাওয়া। আর আপনার লেখার ধরন — await নাকি then — ঠিক করে দেয় যে ক্র্যাশের দিন আপনি কতটা তথ্য হাতে পাবেন।

অনুশীলন

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

  1. ক্রম অনুমান করুন: async function f() { console.log("A"); await null; console.log("B"); } setTimeout(() => console.log("C"), 0); f(); console.log("D"); — কোন ক্রমে ছাপা হবে?

    A, D, B, C. f() ডাকামাত্র প্রথম await পর্যন্ত synchronous চলে — তাই A. await null-এ function থামে, বাকি অংশ একটি microtask হয়ে লাইনে দাঁড়ায়। মূল কোড চলতে থাকে: D. মূল কোড শেষ হলে আগে সব microtask (পর্ব ৩১): B. তারপর পরের task, টাইমারের callback: C — যদিও টাইমার বসানো হয়েছিল সবার আগে, আর বিলম্ব ছিল ০।

  2. ধীর কোড খুঁজুন: একটি পাতা খুললে const user = await getUser(id); const posts = await getPosts(id); const friends = await getFriends(id); const ads = await getAds(user.city); চলে। প্রতিটি ২০০ ms নিলে এখন কত সময় লাগে? কোন await-গুলো একসাথে চালানো যায়, আর নতুন সময় কত?

    এখন ৪ × ২০০ = ৮০০ ms. getPosts আর getFriends শুধু id চায়, যা আগেই আছে — এরা getUser-এর সাথেই শুরু হতে পারে। শুধু getAds নির্ভর করে user.city-এর ওপর। একটি সমাধান: const [user, posts, friends] = await Promise.all([getUser(id), getPosts(id), getFriends(id)]); const ads = await getAds(user.city); — মোট ২০০ + ২০০ = ৪০০ ms. আরও ভালো: getAds যাতে posts আর friends-এর জন্য অপেক্ষা না করে — const userP = getUser(id); const adsP = userP.then((u) => getAds(u.city)); const [user, posts, friends, ads] = await Promise.all([userP, getPosts(id), getFriends(id), adsP]); — এখনো ৪০০ ms, কারণ ads-এর শিকল user-এর ওপর নির্ভরশীল; কিন্তু posts বা friends ধীর হলে এই রূপ সেই দেরি ads-এ যোগ করে না।

  3. সময়সীমা (কোড লিখুন): একটি withTimeout(promise, ms) function লিখুন, যা promise-টি ms-এর মধ্যে পূর্ণ হলে তার মান দেয়, নাহলে TimeoutError (নিজের error class) ছোড়ে। খেয়াল রাখুন, promise সময়মতো শেষ হলে টাইমারটি যেন পরিষ্কার হয়।

    একটি সমাধান: class TimeoutError extends Error { name = "TimeoutError"; } তারপর async function withTimeout(promise, ms) { let timer; const limit = new Promise((_, reject) => { timer = setTimeout(() => reject(new TimeoutError(`${ms} ms পেরিয়ে গেছে`)), ms); }); try { return await Promise.race([promise, limit]); } finally { clearTimeout(timer); } }. যাচাই: await withTimeout(wait(50, "ঠিক"), 100) → "ঠিক"; await withTimeout(wait(200, "দেরি"), 100) → TimeoutError. লক্ষ করুন return await — try-এর ভেতরে, যাতে finally race শেষ হওয়ার পরেই চলে (await ছাড়া return করলেও এখানে finally চলত, কিন্তু race শেষ হওয়ার আগেই টাইমার মুছে যেত, আর timeout কখনো কাজ করত না!)। আর মনে রাখুন: timeout হলেও আসল কাজটি পেছনে চলতে থাকে — সত্যিই বাতিল করতে AbortController, পর্ব ৩৬-এ।

  4. AI অডিট: যেকোনো AI-কে বলুন “JavaScript-এ একটি ফাংশন লেখো যা ১০০ জন ব্যবহারকারীর id-র তালিকা নিয়ে প্রত্যেককে একটি নোটিফিকেশন পাঠায় আর শেষে ‘সব পাঠানো হয়েছে’ লেখে।” তারপর অংশ ৯-এর চেকলিস্ট দিয়ে উত্তরটি পরীক্ষা করুন।

    সাধারণত দেখা যায় তিনটির একটি: (১) ids.forEach(async (id) => { await sendNotification(id); }) তারপর “সব পাঠানো হয়েছে” — বার্তাটি আগেই ছাপা হয়, আর কোনো পাঠানো ব্যর্থ হলে অভিভাবকহীন rejection. (২) for…of-এ await — ঠিক, কিন্তু ১০০টি স্বাধীন অনুরোধ একটার পর একটা; প্রতিটি ৩০০ ms হলে ৩০ সেকেন্ড। (৩) Promise.all(ids.map(sendNotification)) — দ্রুত, কিন্তু একটি ব্যর্থ হলে পুরোটা reject, বাকিগুলো পাঠানো হলো কি না অজানা; আর ১০০০০ id হলে সার্ভারের সীমা। ভালো উত্তর: Promise.allSettled দিয়ে, ১০টি করে ভাগে, শেষে কয়টি সফল আর কোন id-গুলো ব্যর্থ তার হিসাব। AI-কে বলুন: “একসাথে সর্বোচ্চ ১০টি, allSettled দিয়ে, শেষে সফল আর ব্যর্থের সংখ্যা ও ব্যর্থ id-র তালিকা দাও।”

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

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

ক্রমএকটি async function ডাকা থেকে তার await-এর পরের লাইন চলা পর্যন্ত ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. async function-এর শরীর প্রথম await পর্যন্ত সাধারণ কোডের মতো সাথে সাথে চলে
  2. await-এ function থামে, আর ডাকার জায়গায় একটি pending promise ফেরত যায়
  3. ডাকার জায়গার বাকি synchronous কোড চলে, তারপর call stack খালি হয়
  4. অপেক্ষার promise মীমাংসা হয়, আর function-এর বাকি অংশ microtask হিসেবে লাইনে দাঁড়ায়
  5. function থামার জায়গা থেকে আবার চলে — মান নিয়ে, বা সেখানে error ছুড়ে

async function f() { return 5; } — f() ডাকলে কী পাওয়া যায়?

  • সংখ্যা 5
  • 5 দিয়ে পূর্ণ হওয়া একটি Promise
  • undefined দিয়ে পূর্ণ হওয়া একটি Promise
  • SyntaxError, কারণ ভেতরে await নেই

async function a() { console.log(1); await null; console.log(3); } a(); console.log(2); — আউটপুটের ক্রম কী?

  • 1 3 2
  • 2 1 3
  • 1 2 3
  • 2 3 1

ids.forEach(async (id) => { await save(id); }); console.log("সব শেষ"); — এখানে কী ঘটে?

  • “সব শেষ” ছাপা হয় save-গুলো শেষ হওয়ার আগেই
  • প্রতিটি save শেষ হলে তারপর “সব শেষ” ছাপা হয়
  • SyntaxError হয়, কারণ forEach-এ async চলে না
  • কোনো save শুরুই হয় না

তিনটি স্বাধীন অনুরোধ, প্রতিটি ১০০ ms নেয়। কোন লেখায় মোট অপেক্ষা মোটামুটি ১০০ ms হবে?

  • তিনটি আলাদা লাইনে একটার পর একটা await
  • একটি for…of লুপের ভেতরে await
  • for await…of দিয়ে তিনটি অনুরোধ ঘোরা
  • await Promise.all([a(), b(), c()])

একটি সাধারণ (classic) <script>-এর একেবারে বাইরের স্তরে, কোনো function ছাড়া, const data = await loadData(); লিখলে কী হয়?

  • ঠিকমতো চলে, ES2022 থেকে সব script-এ top-level await বৈধ
  • SyntaxError — top-level await শুধু module-এ চলে
  • await উপেক্ষিত হয়, data-তে একটি promise থাকে
  • ব্রাউজার script-টিকে নিজে থেকে module বানিয়ে চালায়

isValid একটি async function, যা false দিয়ে পূর্ণ হয়। if (isValid()) { grant(); } — কী ঘটে?

  • grant() কখনো চলে না, কারণ ফল false
  • TypeError হয়, কারণ শর্তে promise দেওয়া যায় না
  • grant() সবসময় চলে, কারণ promise একটি object আর object truthy
  • if নিজে থেকে promise পূর্ণ হওয়ার অপেক্ষা করে, তাই grant() চলে না

শব্দকোষ

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

মনে রাখুন

  1. async function সবসময় একটি promise ফেরত দেয় — return মানে পূর্ণ, throw মানে rejected; প্রথম await পর্যন্ত এটি সাথে সাথে চলে।
  2. await থামায় শুধু নিজের function-কে; বাকি প্রোগ্রাম চলে, আর পরের অংশ microtask হিসেবে আবার শুরু হয়। ব্যর্থ promise await-এর লাইনে throw হয়, তাই try/catch/finally কাজ করে।
  3. স্বাধীন কাজে লুপে await নয় — Promise.all(items.map(...)); নির্ভরশীল ধাপ, rate limit বা জরুরি ক্রমে একটার পর একটা; আংশিক ব্যর্থতায় allSettled, সময়সীমায় race.
  4. forEach অপেক্ষা করে না — for…of বা Promise.all; পাতায় পাতায় আসা ডেটায় for await…of; top-level await শুধু module-এ।
  5. প্রতিটি promise-এর অভিভাবক চাই — await বা .catch; try-এর ভেতরে return await; async constructor নেই, static factory; await-এর শিকলই সুন্দর async stack trace দেয়।

পরের পর্বে: এই পর্বের সব getSales() আর charge() ছিল নকল, setTimeout দিয়ে বানানো। এবার আসল জিনিস — fetch দিয়ে সার্ভারে HTTP অনুরোধ পাঠানো, status code বোঝা, JSON পড়া আর পাঠানো, আর কেন fetch ৪০৪ পেলেও ব্যর্থ হয় না।

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

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