async/await এতটাই ভালো একটা ধারণা ছিল যে এটা নতুন এক শ্রেণির বাগ তৈরি করে ফেলেছে। এর আগে asynchronous code দেখতেও asynchronous লাগত — callback, .then() chain, পুরোটাই চিৎকার করে বলত "সাবধান, এটা পরে চলবে।" এখন এটা দেখতে হুবহু সাধারণ উপর-থেকে-নিচে code-এর মতো, আর সমস্যাটা এখানেই। কোনো কিছু ক্রমানুসারে চলছে বলে মনে হলে, আপনি সেটা সত্যিই তাই কিনা যাচাই করা বন্ধ করে দেন।
এই চারটাই আমি এখনো সবচেয়ে বেশি ধরি। কোনোটাই স্পষ্ট error দেয় না। এক নজরে সবগুলোই সঠিক মনে হয়। এই কারণেই এগুলো জানা জরুরি।
১. forEach কারও জন্য অপেক্ষা করে না
এটাই আমি সবচেয়ে বেশি দেখি। আপনার একটা array আছে, প্রতিটা item-এর জন্য একটা async কাজ করতে হবে, আর আপনি forEach-এর দিকে হাত বাড়ান কারণ এটাই আপনার চেনা loop।
async function chargeAll(users) {
users.forEach(async (user) => {
await chargeCard(user);
});
console.log("all charged"); // lies
}উচ্চস্বরে পড়লে সঠিক মনে হয়: "প্রতিটা user-এর জন্য চার্জ-এ await করো।" কিন্তু forEach async বোঝে না। এটা আপনার callback কল করে, callback একটা promise ফেরত দেয়, আর forEach সেই promise মেঝেতে ফেলে পরের item-এ চলে যায়। সব চার্জ একসঙ্গে শুরু হয়, আর একটাও শেষ হওয়ার আগেই console.log চলে। একটা card ব্যর্থ হলে, সেই rejection unhandled থেকে যায় — forEach এর জন্য await করছে না, তাই কেউ ধরেও না।
সমাধান নির্ভর করে আপনি আসলে কী চান তার উপর। একটার পর একটা, ক্রম রেখে দরকার? একটা আসল loop ব্যবহার করুন, কারণ for...of await-কে সম্মান করে:
for (const user of users) {
await chargeCard(user); // genuinely sequential now
}সবগুলো একসঙ্গে চালানো ঠিক আছে? Promise.all দিয়ে স্পষ্টভাবে সেটা বলুন:
await Promise.all(users.map((user) => chargeCard(user)));আমি যে নিয়মে থিতু হয়েছি: .forEach-এর ভেতরে await দেখামাত্রই সেটা ভুল। "সম্ভবত ভুল" না — ভুল। ইচ্ছাকৃতভাবে for...of বা Promise.all বেছে নিন।
.forEach-এর ভেতরে একটাawaitএকটা বাগ, কোনো style-এর পছন্দ নয়। forEach promise-টা ফেলে দেয় — কেউ অপেক্ষা করে না, আর একটা rejection unhandled থেকে যায়।
২. যা একসঙ্গে হতে পারত তা ক্রমানুসারে করা
এটা উল্টো ভুল, আর প্রথমটায় একবার পুড়লে এটা করা সহজ হয়ে যায়। আপনি যত্ন করে সবকিছুতে ক্রমানুসারে await করা শুরু করেন — আর এখন একে অপরের সঙ্গে কোনো সম্পর্ক না থাকা তিনটা request পরপর চলে।
const user = await fetchUser(id);
const posts = await fetchPosts(id);
const notifications = await fetchNotifications(id);প্রতিটাতে ২০০ms লাগলে, এই page ৬০০ms অপেক্ষা করে। কিন্তু এদের কেউ কারও উপর নির্ভর করে না — fetchPosts-এর fetchUser-এর result দরকার নেই। এদের লাইনে দাঁড় করানোর কোনো কারণ নেই। একসঙ্গে চালান:
const [user, posts, notifications] = await Promise.all([
fetchUser(id),
fetchPosts(id),
fetchNotifications(id),
]);এখন এটা ২০০ms, কারণ এরা একসঙ্গে ওভারল্যাপ করে। লক্ষণটা সহজ: পরপর কয়েকটা await থাকলে আর পরেরটা আগেরটার result ব্যবহার না করলে, এদের পরপর থাকার কথা না। কেবল তখনই ক্রমানুসারে await করুন যখন দ্বিতীয়টার সত্যিই প্রথমটা দরকার।
৩. কিছু ব্যর্থ হওয়া মাত্র Promise.all হাল ছেড়ে দেয়
Promise.all-এর একটা ধারালো দিক আছে যা মানুষ ভুলে যায়: একটা promise reject হওয়া মাত্র এটা reject করে, আর আপনি বাকি সব result হারান — যেগুলো ইতিমধ্যে সফল হয়েছিল সেগুলোও।
// If any single row fails, you get nothing back — not even the good rows.
const results = await Promise.all(rows.map(importRow));page load-এর মতো কিছুর জন্য, যেখানে একটা ব্যর্থতা মানেই পুরো page ভেঙে যাওয়া, সেটা ঠিক আছে। কিন্তু একটা batch job-এর জন্য — শখানেক row import করা, শখানেক email পাঠানো — সাধারণত জানতে চান কোনগুলো কাজ করেছে আর কোনগুলো না, শুধু row ৪৩-এ খারাপ date ছিল বলে পুরো batch ফেলে দিতে চান না। এই কাজের জন্যই Promise.allSettled:
const results = await Promise.allSettled(rows.map(importRow));
const failed = results.filter((r) => r.status === "rejected");
console.log(`${rows.length - failed.length} imported, ${failed.length} failed`);allSettled কখনো reject করে না। প্রতিটা entry { status: "fulfilled", value } অথবা { status: "rejected", reason } হিসেবে ফিরে আসে, তাই এটা সম্পূর্ণ-না-কিছুই-না ভান না করে আংশিক সাফল্যের ভিত্তিতে কাজ করতে পারেন।
পার্থক্যটা মনে রাখার সহজ উপায়
Promise.all মানে "সব নয়তো কিছুই না" — একটা ব্যর্থতা সবকিছু ডুবিয়ে দেয়।
Promise.allSettled মানে "প্রতিটার কী হলো বলো" — এটা সবসময় একটা পূর্ণ
রিপোর্ট নিয়ে resolve হয়। একটা ব্যর্থতা মানেই সব শেষ হলে all ব্যবহার
করুন; আংশিক সাফল্য একটা বাস্তব ফলাফল হলে allSettled ব্যবহার করুন।
৪. যে হারিয়ে যাওয়া await আপনার error খেয়ে ফেলে
এটা বিরক্তিকর কারণ এটা কাজ করে। সাধারণত। যতক্ষণ না করে — আর তখন এটা নীরবে ব্যর্থ হয়।
async function handler(req, res) {
try {
saveToDatabase(req.body); // no await
res.send("saved");
} catch (err) {
res.status(500).send("failed");
}
}আপনি saveToDatabase কল করেছেন, কিন্তু await করতে ভুলে গেছেন। function সঙ্গে সঙ্গে একটা promise ফেরত দেয়, try block শেষ হয়, res.send("saved") চলে — আর save শুরুই হওয়ার আগে ব্যবহারকারীকে বলে দিয়েছেন এটা সংরক্ষিত হয়েছে। save তারপর ব্যর্থ হলে, সেই rejection try/catch-এর বাইরে ঘটে, কারণ try block ততক্ষণে বেরিয়ে গেছে। আপনার error handling ঠিক সেখানেই আছে আর সেটা কখনো চলেই না। যে write ব্যর্থ হলো তার জন্য ব্যবহারকারী একটা 200 পেয়েছেন।
try/catch কেবল তখনই একটা async error ধরে যদি try-এর ভেতরের জিনিসটা আপনি await করেন। await নেই তো catch-ও নেই। একটা শব্দ যোগ করুন:
await saveToDatabase(req.body);async বাগকে কঠিন বানানোর অর্ধেক কারণ হলো একটা ভুলে যাওয়া await error দেয় না — এটা শুধু চুপচাপ ভুল সময়ে ভুল কাজ করে। কোনো asynchronous জিনিস অদ্ভুত আচরণ করলে, "কোনো await কি বাদ পড়েছে?" জিজ্ঞেস করার মতো প্রথম প্রশ্ন এটাই।
এগুলোর মধ্যে যে যোগসূত্র
এদের প্রতিটাই পড়তে সঠিক লাগে। এটাই পুরো শিক্ষা। async/await যেহেতু asynchronous code-কে synchronous দেখায়, আপনার চোখ সেটাকে আর asynchronous হিসেবে দেখা বন্ধ করে দেয় — আর এই বাগগুলো ঠিক code দেখতে কেমন আর সেটা আসলে কীভাবে চলে তার মাঝের ফাঁকেই থাকে।
review করার আগেই বেশিরভাগ ধরার কিছু অভ্যাস:
.forEach-এর ভেতরে কখনোawaitরাখবেন না। ক্রমানুসারের জন্যfor...of, সমান্তরালের জন্যPromise.all— আর ইচ্ছাকৃতভাবে বেছে নিন।- পরপর কিছু await করছেন? দেখুন এরা সত্যিই একে অপরের উপর নির্ভর করে কিনা। না করলে,
Promise.allকরুন। - আংশিক ব্যর্থতা একটা বাস্তব case কিনা জিজ্ঞেস করুন। হলে,
allনয়,allSettled। - প্রতিটা un-awaited async call-কে সন্দেহজনক ভাবুন। বেশিরভাগ সময় হারিয়ে যাওয়া
await-ই বাগটা।
এর কোনোটাই অদ্ভুত কিছু না। এই একই কয়েকটা ভুল ভালো developer-রাও করেন, ঠিক এই কারণে যে আধুনিক syntax এতটাই স্বস্তিদায়ক যে মনোযোগ দেওয়া বন্ধ হয়ে যায়। code যেখানে চুপ হয়ে যায় সেখানেই মনোযোগ ধরে রাখুন।