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

error সামলানো — throw, try/catch, custom error আর cause

JavaScript Error Handling — Error Types, throw & Stack Unwinding, try/catch/finally, Custom Errors, cause, Result Objects and Global Handlers

Taught in Bangla, this lesson shows how JavaScript errors travel and how to handle them well: the built-in error types and the Error object, throw and stack unwinding, try/catch/finally flow including return traps, custom error classes with ES2022 cause, result objects versus exceptions, errors try/catch cannot catch in callbacks and promises, global handlers, and useful logging.

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

  • Syntax error, runtime exception, logic bug, operational আর programmer error — কোনটি কী, কোনটি কে ধরে
  • Built-in error টাইপগুলো কখন আসে, আর Error object-এর name, message, stack, cause
  • throw করলে call stack-এ ঠিক কী ঘটে, আর try/catch/finally-এর প্রবাহ — সফল, ব্যর্থ আর return-সহ
  • Custom error class, instanceof দিয়ে বাছাই, rethrow আর cause দিয়ে error-এর শিকল
  • কখন exception নয়, result object; কোন error try/catch ধরতে পারে না; global handler, log, debugging আর AI-এর লেখা error-কোড যাচাই

১ · error কী — প্রোগ্রামের “আর এগোতে পারছি না” বার্তা

একটি দৃশ্য দিয়ে শুরু করি। নুসরাত একটি অনলাইন দোকানের চেকআউট পাতা বানিয়েছেন। একদিন এক ক্রেতা অর্ডার দিলেন, বাটন চাপলেন — কিছুই হলো না। পাতা স্থির, কোনো বার্তা নেই। ক্রেতা আবার চাপলেন, তারপর বিরক্ত হয়ে অন্য দোকানে চলে গেলেন। পরে নুসরাত DevTools খুলে দেখলেন console-এ লাল লেখা:

Uncaught TypeError: Cannot read properties of undefined (reading 'phone')
    at buildInvoice (checkout.js:41:28)
    at placeOrder (checkout.js:17:5)

ক্রেতা ফোন নম্বরের ঘর ফাঁকা রেখেছিলেন, কোড ধরে নিয়েছিল ঘরটি সবসময় থাকবে। আসল সমস্যা কিন্তু শুধু ফাঁকা ঘর নয়। সমস্যা হলো প্রোগ্রামটি ভুলটা কাউকে জানায়নি — না ক্রেতাকে (“ফোন নম্বর দিন”), না নুসরাতকে (কোনো log বা রিপোর্ট নেই)। এই পর্বের পুরো বিষয়টাই এখানে: ভুল হবেই; প্রশ্ন হলো ভুলটা কোথায় ধরা পড়বে, কে জানবে, আর তারপর কী হবে।

মূল ধারণা

যখন কোনো কোড তার কাজ স্বাভাবিক পথে শেষ করতে পারে না, তখন সে একটি exception ছোড়ে — JavaScript-এ সাধারণত একটি Error object. ছোড়ামাত্র সেই function-এর বাকি কাজ বাতিল হয়, আর error-টি ডাকার দিকে (call stack-এ নিচে) ফিরতে থাকে, যতক্ষণ না কোনো try/catch তাকে ধরে। কেউ না ধরলে সেটি uncaught — ব্রাউজার console-এ জানায় আর ওই কাজটি থেমে যায়, Node.js পুরো প্রোগ্রাম বন্ধ করে দেয়। ভালো error handling মানে তিনটি কাজ: ঠিক জায়গায় জোরে ব্যর্থ হওয়া, প্রসঙ্গ যোগ করা, আর কেবল সেখানেই সামলানো যেখানে সত্যিই কিছু করা সম্ভব।

bKash-এ টাকা পাঠানোর কথা ভাবুন। আপনি নম্বর আর টাকার পরিমাণ দিলেন, কিন্তু ব্যালান্স কম। অ্যাপ চুপচাপ বসে থাকে না, আবার পুরো ফোনও বন্ধ করে দেয় না — স্পষ্ট বলে “আপনার ব্যালান্স যথেষ্ট নয়”, আর লেনদেন বাতিল করে। ভেতরে কোথাও একটি নিয়ম ভাঙা (ব্যালান্স < পরিমাণ) ধরা পড়েছে, সেই খবর ধাপে ধাপে ওপরে উঠে এসেছে, আর একেবারে ওপরের স্তর — পর্দা — ঠিক করেছে আপনাকে কী বলবে। Exception-এর যাত্রাও ঠিক এমন: নিচে ধরা পড়ে, ওপরে সামলানো হয়।

সব ভুল এক রকম নয় — চার ধরনের সমস্যা

কোন ভুল কখন ধরা পড়ে

চালানোর আগে → চলার সময় → কেউ টেরই পায় না

  1. Syntax errorif (x > 1 {চালানোর আগেই parser থামায়
  2. Runtime exceptionundefined.phoneচলতে চলতে থেমে যায়
  3. Operational errorনেটওয়ার্ক বন্ধকোড ঠিক, পরিবেশ খারাপ
  4. Logic bugtotal = a - bকোনো error নেই, ফল ভুল
ছবি ২৯.১ — Syntax error-এ script শুরুই হয় না, exception চলার মাঝে থামায়, operational error আগে থেকে ভেবে রাখা যায়, আর logic bug-এর কোনো সংকেতই আসে না।
  • Syntax error — চালানোর আগে। JavaScript engine কোড চালানোর আগে পুরো script পড়ে (parse করে)। একটি বন্ধনী বাদ পড়লে SyntaxError: Unexpected token '{' — আর সেই script-এর একটি লাইনও চলে না (পর্ব ০৫)। এগুলো try/catch দিয়ে ধরা যায় না, কারণ try/catch নিজেই তো তখনো চালু হয়নি; ধরা পড়ে editor, ESLint বা প্রথমবার চালানোর সময়ই।
  • Runtime exception — চলার সময়। ব্যাকরণ ঠিক, কিন্তু চলতে চলতে এমন কিছু করতে বলা হলো যা অসম্ভব: undefined-এর property পড়া, যা function নয় তাকে ডাকা, অজানা নাম ব্যবহার। এগুলোই exception — try/catch এদের জন্য।
  • Logic bug — কেউ টের পায় না। ছাড়ের হিসাবে +-এর জায়গায় -, লুপ এক ঘর কম। কোনো error আসে না, প্রোগ্রাম আনন্দে ভুল ফল দেখায়। সবচেয়ে বিপজ্জনক এগুলোই; ধরতে লাগে টেস্ট (পর্ব ৪৮), assert আর নিজের যাচাই।

আরেকটি ভাগ পেশাদার জগতে খুব কাজের — ভুলটা কার? Operational error হলো কোড ঠিক থাকা সত্ত্বেও বাইরের কারণে ব্যর্থতা: ইন্টারনেট চলে গেল, সার্ভার ৫০৩ দিল, ব্যবহারকারী ভুল তারিখ লিখলেন, ব্যালান্স কম। এগুলো প্রত্যাশিত — আগে থেকে ভেবে সামলাতে হয় (আবার চেষ্টা, স্পষ্ট বার্তা, বিকল্প পথ)। Programmer error হলো কোডের নিজের বাগ: ভুল টাইপের argument পাঠানো, undefined ধরে না রাখা। এগুলো চলার সময় “সামলানো” যায় না — catch করে চেপে রাখলে বাগ শুধু লুকায়; সমাধান কোড ঠিক করা। Node.js-জগতে এই ভাগটি জনপ্রিয় করেছিল Joyent-এর একটি পরিচিত নির্দেশিকা, আর আজও ভালো error handling-এর প্রথম প্রশ্ন এটাই: “এটা কি প্রত্যাশিত ব্যর্থতা, নাকি বাগ?”

প্রথম দিকের ভাষায় ভুল জানানোর একমাত্র উপায় ছিল ফেরত-মান — C-তে -1 ফেরত দিয়ে errno-তে কারণ লেখা। ১৯৬০-এর দশকে PL/I-এর “ON condition”, ১৯৭০-এর দশকে CLU আর Lisp-এর পরীক্ষা থেকে আজকের exception-এর ধারণা গড়ে ওঠে; C++ আর Java একে মূলধারায় আনে। JavaScript-এর প্রথম সংস্করণে (১৯৯৫) try/catch ছিলই না — এটি এসেছে ES3-এ (১৯৯৯)। পুরো গল্প ভাবনার প্রশ্ন ১-এ।

২ · Error object আর built-in error-এর পরিবার

JavaScript যখন নিজে কোনো ভুল ধরে, সে একটি Error object বানিয়ে ছোড়ে। এই object-এর তিনটি জিনিস প্রায় সবসময় পাবেন:

  • name — error-এর ধরন, যেমন "TypeError". কোডে সিদ্ধান্ত নেওয়ার জন্য এটিই নির্ভরযোগ্য।
  • message — মানুষের পড়ার বার্তা, যেমন "o.pay is not a function". Engine-ভেদে আর সংস্করণভেদে বদলায়, তাই কোডে বার্তা মিলিয়ে সিদ্ধান্ত নেবেন না।
  • stack — error কোথায় তৈরি হলো আর কোন কোন function পার হয়ে এলো, তার তালিকা (stack trace). মজার তথ্য: stack আজও ECMAScript মানদণ্ডের অংশ নয় — সব বড় engine দেয়, কিন্তু নিজের নিজের আকারে (ভাবনার প্রশ্ন ৩)।

Edge বা Chrome-এ (V8 engine) একটি stack দেখতে মোটামুটি এরকম — ওপরের লাইনে name: message, তারপর প্রতিটি লাইনে একটি function, ফাইল, লাইন আর কলাম:

RangeError: সীমা ৫০০ টাকা
    at c (app.js:2:27)
    at b (app.js:6:16)
    at a (app.js:11:5)

Firefox একই তথ্য লেখে c@app.js:2:27 আকারে। পড়ার নিয়ম সহজ: ওপরের লাইন হলো ভুলের জায়গা, নিচের দিকে গেলে “কে ডেকেছিল”. প্রায়ই ওপরের কয়েকটি লাইন library-র ভেতরের; নিচে নামতে নামতে প্রথম যে লাইনে নিজের ফাইলের নাম পাবেন, সাধারণত সেখান থেকেই খোঁজা শুরু করবেন।

কোন error কখন আসে

নিচের বার্তাগুলো Edge-এ (V8) চালিয়ে নেওয়া; Node.js-এও একই engine, তাই প্রায় এক। Firefox বা Safari-র বার্তা ভিন্ন ভাষায় লেখা হয়, কিন্তু name একই থাকে।

টাইপকখন আসেউদাহরণ ও বার্তা (V8)
TypeErrorমানটি দিয়ে চাওয়া কাজ করা যায় না — সবচেয়ে সাধারণu.name (u = undefined) → Cannot read properties of undefined (reading 'name'); o.pay() → o.pay is not a function; const-এ নতুন মান → Assignment to constant variable.
RangeErrorটাইপ ঠিক, কিন্তু মান সীমার বাইরেnew Array(-1) → Invalid array length; (1.5).toFixed(200) → toFixed() digits argument must be between 0 and 100; অন্তহীন recursion → Maximum call stack size exceeded
ReferenceErrorনামটিই খুঁজে পাওয়া যায় না, বা TDZ-এ (পর্ব ০৭)totl + 1 → totl is not defined; let-এর আগে ব্যবহার → Cannot access 'x' before initialization
SyntaxErrorব্যাকরণ ভুল — কোডে, বা চলার সময় পড়া লেখায়JSON.parse("") → Unexpected end of JSON input; JSON.parse("{bad") → Expected property name or '}' in JSON at position 1
URIErrorভাঙা URL-encodingdecodeURIComponent("%") → URI malformed
AggregateErrorএকসাথে অনেকগুলো ব্যর্থতা (ES2021)Promise.any-এর সব promise ব্যর্থ হলে; ভেতরের error-গুলো থাকে err.errors array-তে
EvalErrorমূলত ইতিহাসের জন্য টিকে আছেভাষার মানদণ্ড নিজে আর ব্যবহার করে না; তবে Edge/Chrome-এ CSP (পর্ব ৪৩) eval আটকালে এই টাইপটিই দেখা যায়

সবগুলোই সাধারণ Error-এর “সন্তান” — new TypeError("x") instanceof Error হলো true. এর বাইরে ব্রাউজারের নিজস্ব DOMException আছে (পর্ব ১৯-এর DataCloneError, fetch বাতিল করলে AbortError), আর Node.js-এর error-এ থাকে বাড়তি code field, যেমন ফাইল না পেলে err.code === "ENOENT" (পর্ব ৪৯)।

একটি সূক্ষ্ম পার্থক্য মনে রাখুন। কোডের SyntaxError (ভুল বন্ধনী) script চালানোর আগেই ধরা পড়ে, তাই try/catch তাকে ধরতে পারে না। কিন্তু JSON.parse বা new Function চলার সময় একটি লেখা পড়ে — সেখানে ব্যাকরণ ভুল হলে SyntaxError একটি সাধারণ runtime exception, try/catch দিয়ে দিব্যি ধরা যায়।

৩ · throw আর stack unwinding — error কোন পথে ওঠে

Engine নিজে error ছোড়ে, আর আপনিও ছুড়তে পারেন — throw statement দিয়ে। নিয়ম ভাঙা অবস্থা ধরা পড়লে চুপ করে ভুল মান ফেরত দেওয়ার বদলে সাথে সাথে থামুন:

function withdraw(account, amount) {
  if (!Number.isSafeInteger(amount) || amount <= 0) {
    throw new RangeError(`টাকার পরিমাণ ঠিক নয়: ${amount}`);
  }
  account.balance -= amount;        // এখানে পৌঁছানো মানে amount বৈধ
  return account.balance;
}

শুরুর এই শর্তটি একটি guard clause (পর্ব ১১) — অবৈধ অবস্থা দরজাতেই আটকানো, যাতে মূল কাজ নিশ্চিন্তে লেখা যায়। throw-এর পরে function-এর আর কোনো লাইন চলে না, return-ও না।

যেকোনো মান ছোড়া যায় — কিন্তু ছুড়বেন শুধু Error

JavaScript খুব উদার: throw "ব্যালান্স কম", throw 404, throw { code: 42 } — সবই বৈধ। কিন্তু string ছুড়লে ধরার জায়গায় পাবেন শুধু একটি string: err.message হবে undefined, err.stack হবে undefined — কোথায় ভুল হলো তার কোনো চিহ্ন নেই। instanceof দিয়ে ধরন বাছাই করা যায় না, error monitoring টুল ঠিকমতো দেখাতে পারে না। Error object তৈরির মুহূর্তেই stack রেকর্ড হয় — এটাই তার আসল মূল্য। তাই নিয়ম: সবসময় throw new Error(...) বা তার কোনো সন্তান। ESLint-এর no-throw-literal নিয়ম ঠিক এটাই পরীক্ষা করে।

Stack unwinding — ধাপে ধাপে

পর্ব ০৫-এ দেখেছেন, প্রতিটি function ডাকলে call stack-এ একটি frame বসে। throw হলে engine সেই স্তূপ উল্টো দিকে খুলতে থাকে — একে বলে stack unwinding. প্রতিটি frame-এ সে খোঁজে: “এখানে কি কোনো try আছে যার ভেতরে এই ডাকটি ঘটেছে?” না থাকলে frame সরিয়ে দেয় — সেই function-এর বাকি লাইন চিরতরে বাদ। নিচের ডায়াগ্রামে a() ডাকে b()-কে, b() ডাকে c()-কে, আর try/catch আছে শুধু a()-তে:

1function c(amount) {2  if (amount > 500) throw new RangeError("সীমা ৫০০ টাকা");3  return amount;4}5function b(amount) {6  const sent = c(amount);7  console.log("b: পাঠানো হলো", sent);8}9function a() {10  try {11    b(900);12    console.log("a: সব ঠিক");13  } catch (err) {14    console.log("a ধরল:", err.message);15  }16  console.log("a: পরের কাজ");17}18a();
এই মুহূর্তে
call stack
error
বাদ পড়া লাইন
  1. a() ডাকা হলো, try-এ ঢুকল। লাইন ১৮ থেকে a চালু, call stack-এ এখন একটি frame. লাইন ১০-এ try শুরু — engine মনে রাখে: “এই frame-এ একটি catch অপেক্ষা করছে।” তারপর লাইন ১১-এ b(900) ডাকা।
  2. b ডাকল c-কে। Stack এখন a → b → c. b লাইন ৬-এ c(900)-এর ফলের জন্য অপেক্ষা করছে, a অপেক্ষা করছে b-এর জন্য। খেয়াল করুন: b আর c-তে কোনো try নেই।
  3. লাইন ২: throw. ৯০০ > ৫০০, তাই একটি RangeError তৈরি হলো (এই মুহূর্তের stack-ও তার ভেতরে রেকর্ড হলো) আর ছোড়া হলো। c সাথে সাথে থামল — লাইন ৩-এর return আর কখনো চলবে না। c কোনো মান ফেরত দেয়নি, দিয়েছে একটি exception.
  4. Unwinding: c আর b সরে গেল। c-তে try নেই, frame সরানো হলো। Error পৌঁছাল b-এর লাইন ৬-এ — সেখানেও try নেই। তাই sent-এ কোনো মান বসল না, লাইন ৭-এর log চলল না, b-এর frame-ও সরল। মাঝের function-গুলো কিছু টেরই পেল না — ঠিক এটাই exception-এর শক্তি: মাঝের স্তরকে প্রতিটি ডাকের পর “ভুল হয়েছে কি?” লিখতে হয় না।
  5. a-এর catch ধরল। লাইন ১১-এর ডাকটি try-এর ভেতরে ছিল, তাই engine এখানে থামল। Try-এর বাকি অংশ (লাইন ১২, “সব ঠিক”) বাদ, চলল catch (err) — err হলো সেই একই RangeError object. আউটপুট: a ধরল: সীমা ৫০০ টাকা.
  6. তারপর স্বাভাবিক জীবন। Catch শেষ হলে error “সামলানো” হয়ে গেছে, প্রোগ্রাম try/catch-এর পরের লাইন ১৬ থেকে চলতে থাকে: a: পরের কাজ. যদি a-তেও catch না থাকত, error stack-এর একদম তলায় পৌঁছে uncaught হতো — console-এ Uncaught RangeError: সীমা ৫০০ টাকা, আর লাইন ১৬-ও চলত না।
throw → বর্তমান function থামে → frame-এর পর frame সরে → প্রথম যে try পাওয়া যায়, তার catch চলে

৪ · try, catch, finally — ধরা, সামলানো, পরিষ্কার করা

পুরো কাঠামোর তিনটি অংশ। try { … } — যে কোড ব্যর্থ হতে পারে। catch (err) { … } — ব্যর্থ হলে কী করবেন; err-এ আসে ছোড়া মানটি। finally { … } — সফল হোক বা ব্যর্থ, শেষে যা করতেই হবে: লোডিং চিহ্ন সরানো, বাটন আবার চালু করা, ফাইল বা ডেটাবেস সংযোগ বন্ধ করা। try-এর সাথে catch বা finally — অন্তত একটি লাগবে; দুটোও একসাথে চলে। নিচের পাঁচটি অবস্থায় কোন block কখন চলে, খেয়াল করুন:

অবস্থা ১: সব ঠিকcatch বাদ, finally চলে
try {
  console.log("১. টাকা কাটা হলো");
  console.log("২. রসিদ তৈরি");
} catch (err) {
  console.log("সমস্যা:", err.message);   // চলল না
} finally {
  console.log("৩. সংযোগ বন্ধ");
}
→ ১. টাকা কাটা হলো · ২. রসিদ তৈরি · ৩. সংযোগ বন্ধ
অবস্থা ২: মাঝপথে errortry-এর বাকিটা বাদ
try {
  console.log("১. টাকা কাটা হলো");
  throw new Error("প্রিন্টারে কাগজ নেই");
  console.log("২. রসিদ তৈরি");           // বাদ
} catch (err) {
  console.log("সমস্যা:", err.message);
} finally {
  console.log("৩. সংযোগ বন্ধ");
}
→ ১. টাকা কাটা হলো · সমস্যা: প্রিন্টারে কাগজ নেই · ৩. সংযোগ বন্ধ
অবস্থা ৩: catch নেই, শুধু finallyপরিষ্কার করে error ওপরে যায়
function save() {
  try {
    throw new Error("ডিস্ক ভর্তি");
  } finally {
    console.log("লোডিং চিহ্ন সরানো");
  }
  console.log("সেভ শেষ");                 // বাদ
}
save();   // → লোডিং চিহ্ন সরানো, তারপর
          //   Uncaught Error: ডিস্ক ভর্তি (কেউ না ধরলে)
অবস্থা ৪: try-এ returnfinally আগে চলে, তারপর ফেরত
function getStatus() {
  try {
    return "সফল";          // ফেরত মান ঠিক হলো, কিন্তু এখনই ফেরা নয়
  } finally {
    console.log("finally চলল");
  }
}
console.log(getStatus());
→ finally চলল
→ সফল
অবস্থা ৫: finally-তে returnসবকিছু মুছে দেয় — ফাঁদ!
function a() { try { return "try"; } finally { return "finally"; } }
function b() { try { throw new Error("জরুরি"); } finally { return "চুপ"; } }

console.log(a());   // → finally   (try-এর ফল হারাল)
console.log(b());   // → চুপ       (error-টাই উধাও!)
  1. সব ঠিক থাকলে। try-এর প্রতিটি লাইন চলে, catch পুরোপুরি বাদ পড়ে, তারপর finally. সাধারণ প্রবাহে catch block-এর কোনো খরচ নেই — এটি শুধু অপেক্ষা করে।
  2. মাঝপথে error. লাইন ৩-এ throw হতেই try-এর বাকি অংশ (রসিদ তৈরি) বাদ, নিয়ন্ত্রণ যায় catch-এ, তারপর finally. লক্ষ করুন: “টাকা কাটা হলো” আগেই ঘটে গেছে — exception আগের কাজ ফেরত আনে না। আসল লেনদেনে তাই ডেটাবেস transaction বা “আগে যাচাই, পরে কাটা” নকশা লাগে।
  3. catch ছাড়া শুধু finally. এখানে error সামলানোর কেউ নেই, কিন্তু পরিষ্কারের কাজ আছে। finally চলে, তারপর একই error আবার ওপরের দিকে যাত্রা শুরু করে — save()-এর বাকি লাইন বাদ, আর ডাকার জায়গায় কেউ না ধরলে uncaught. “আমি সামলাতে পারব না, কিন্তু নিজের জিনিস গুছিয়ে যাব” — এই নকশাই try/finally.
  4. try-এর ভেতরে return. return "সফল" ফেরত মান ঠিক করে, কিন্তু function সাথে সাথে বের হয় না — আগে finally চলে। তাই আউটপুটে “finally চলল” আসে “সফল”-এর আগে। এ কারণেই finally-তে রাখা পরিষ্কারের কাজ কখনো ফাঁকি যায় না — যে পথেই বের হোন।
  5. finally-র ভেতরে return — করবেন না। finally-তে return লিখলে সেটি try-এর ফেরত মান বদলে দেয় ("finally"), আর আরও খারাপ — চলমান exception-টাকেই মুছে দেয় (b() কোনো error ছাড়াই "চুপ" দিল)। জরুরি ভুল নিঃশব্দে উধাও। এই আচরণ Edge-এ যাচাই করা; ESLint-এর no-unsafe-finally নিয়ম এটি ধরে। finally শুধু পরিষ্কারের জন্য — সেখানে return, throw বা break নয়।

Optional catch binding আর rethrow

কখনো error-এর বিস্তারিত লাগে না — শুধু জানলেই হয় যে ব্যর্থ হয়েছে। ES2019 থেকে তখন (err) বাদ দেওয়া যায় — একে বলে optional catch binding:

function isValidJson(text) {
  try {
    JSON.parse(text);
    return true;
  } catch {                 // err লাগছে না, তাই নাম দেওয়ারও দরকার নেই
    return false;
  }
}

কিন্তু সাবধান: এটি যত সহজ, তত সহজে অপব্যবহার হয়। উপরের function-টি ঠিক আছে, কারণ এখানে “ব্যর্থ” হওয়াই প্রত্যাশিত উত্তর। একটি বড় কাজের চারপাশে try { … } catch {} বসানো মানে কিন্তু সব বাগ লুকিয়ে ফেলা।

catch-এর সোনালি নিয়ম: যা চেনেন আর সামলাতে পারেন, শুধু সেটাই ধরুন; বাকিটা আবার ছুড়ে দিন। JavaScript-এ Java বা Python-এর মতো catch (TypeError e) লেখা যায় না — catch সব ধরে। তাই ভেতরে বাছাই করতে হয়, আর অচেনা হলে rethrow:

try {
  const settings = JSON.parse(savedText);
  applyTheme(settings.theme);       // এখানে বাগ থাকলে TypeError
} catch (err) {
  if (err instanceof SyntaxError) {
    applyTheme("light");            // ভাঙা সেটিংস — ডিফল্টে ফিরুন, এটা প্রত্যাশিত
  } else {
    throw err;                      // অন্য কিছু (বাগ) — চেপে রাখবেন না
  }
}

throw err একই object আবার ছোড়ে, তাই আসল stack অক্ষত থাকে — নতুন Error বানিয়ে শুধু বার্তা কপি করলে সেটা হারাত। আর try block যত ছোট রাখবেন, “কোন লাইনের error ধরছি” তত পরিষ্কার থাকবে।

৫ · নিজের error class আর cause — প্রসঙ্গ যোগ করা

Built-in টাইপগুলো engine-এর ভাষায় কথা বলে: “TypeError”, “SyntaxError”. কিন্তু আপনার অ্যাপের ব্যর্থতা ব্যবসার ভাষায়: “ফোন নম্বর ভুল”, “ব্যালান্স কম”, “অর্ডার লোড হয়নি”. এদের জন্য নিজের error class বানান — পর্ব ২৬-এর extends দিয়ে (inheritance):

class InsufficientFundsError extends Error {
  constructor(balance, amount) {
    super(`ব্যালান্স ${balance} টাকা, চাওয়া হয়েছে ${amount} টাকা`);
    this.name = "InsufficientFundsError";
    this.balance = balance;          // ধরার জায়গায় কাজে লাগবে
    this.shortBy = amount - balance;
  }
}

function withdraw(account, amount) {
  if (amount > account.balance) throw new InsufficientFundsError(account.balance, amount);
  account.balance -= amount;
}

তিনটি জিনিস লক্ষ করুন। super(message) Error-এর নিজের constructor চালায় — message বসে, stack রেকর্ড হয়। this.name না দিলে name থাকত উত্তরাধিকারে পাওয়া "Error", আর stack-এর প্রথম লাইনেও তাই দেখাত। আর বাড়তি field (balance, shortBy) ধরার জায়গাকে বার্তা পার্স না করেই সিদ্ধান্ত নিতে দেয়: “আরও ৪০০ টাকা যোগ করুন” দেখানো সহজ হয়। এখন err instanceof InsufficientFundsError আর err instanceof Error — দুটোই true.

Error cause — নিচের error হারিয়ে না ফেলে ওপরে প্রসঙ্গ যোগ

একটি চেনা সমস্যা: অর্ডার লোড করতে গিয়ে সার্ভারের উত্তর JSON হিসেবে পড়া হলো, আর এলো SyntaxError: Unexpected token '<'…. Log-এ শুধু এটুকু থাকলে কেউ বুঝবে না কোন অর্ডার, কোন কাজ। আবার যদি catch করে নতুন new Error("অর্ডার লোড হয়নি") ছোড়েন, তাহলে আসল কারণ (সার্ভার HTML পাঠিয়েছে) হারিয়ে যায়। ES2022 এর সমাধান দিয়েছে — error cause: দ্বিতীয় argument-এ { cause: err } দিলে নতুন error-এর cause property-তে আসলটি জুড়ে থাকে। ধাপে ধাপে দেখুন:

1class OrderLoadError extends Error { name = "OrderLoadError"; }2function readOrder(text) {3  try {4    return JSON.parse(text);5  } catch (err) {6    throw new OrderLoadError("অর্ডার A-17 লোড হয়নি", { cause: err });7  }8}9try {10  readOrder("<html>502</html>");11} catch (e) {12  console.log(e.name + ": " + e.message);13  console.log("কারণ: " + e.cause.name + ": " + e.cause.message);14}
হাতে থাকা error
বর্তমান
প্রসঙ্গ জানা?
cause
  1. সার্ভারের উত্তর পড়তে দেওয়া হলো। বাইরের try (লাইন ৯) থেকে readOrder ডাকা হলো একটি HTML error পাতা দিয়ে — সার্ভার ভুলে JSON-এর বদলে 502 পাতা পাঠিয়েছে, বাস্তবে যা প্রায়ই ঘটে (পর্ব ১৯)।
  2. নিচু স্তরে SyntaxError. JSON.parse প্রথম অক্ষর <-এই আটকে গেল। এই error শুধু জানে “লেখাটি JSON নয়” — কোন অর্ডার, কোন ব্যবহারকারী, কোন কাজ, কিছুই জানে না। এভাবেই ওপরে গেলে log-এ একটি প্রসঙ্গহীন লাইন পড়ে থাকবে।
  3. readOrder-এর catch ধরল। এই স্তর জানে সে কী করছিল — অর্ডার লোড। কিন্তু সে ব্যবহারকারীকে কী দেখাবে, তা ঠিক করার জায়গা নয়। তাই সে error “সামলাবে” না, শুধু অর্থপূর্ণ করবে।
  4. মুড়ে নতুন error ছোড়া। new OrderLoadError("অর্ডার A-17 লোড হয়নি", { cause: err }) — বার্তায় ব্যবসার প্রসঙ্গ, আর cause-এ আসল SyntaxError অক্ষত, তার নিজের stack-সহ। আমাদের class-এ constructor লেখা নেই, তাই ডিফল্ট constructor দুটো argument-ই Error-কে দিয়ে দেয়; name বসে class field দিয়ে।
  5. ওপরের স্তর পেল অর্থপূর্ণ error. বাইরের catch এখন e instanceof OrderLoadError দেখে ঠিক করতে পারে: ব্যবহারকারীকে “অর্ডার লোড হয়নি, একটু পরে আবার চেষ্টা করুন” দেখাও। প্রথম আউটপুট লাইন সেটাই।
  6. শিকল বেয়ে আসল কারণে। Log বা debugging-এর জন্য e.cause থেকে নিচের error পড়া যায় — প্রয়োজনে e.cause.cause… যতদূর শিকল যায়। দ্বিতীয় লাইনে আসল V8 বার্তা: সার্ভার HTML পাঠিয়েছে। Node.js-এ পুরো error console.log করলে নিচে আলাদা [cause]: SyntaxError… অংশও ছাপা হয়। নিয়ম মনে রাখুন: প্রতিটি স্তর নিজের জানা প্রসঙ্গ যোগ করে, কিন্তু নিচের তথ্য মুছে দেয় না।

এবার পুরো ছবিটা একসাথে চালিয়ে দেখুন — একটি অর্ডার ফর্মের যাচাই (ValidationError, কোন ঘরে ভুল সেটা field-এ), cause দিয়ে মোড়া JSON-এর ভুল, আর instanceof দিয়ে বাছাই করে সামলানো:

class ValidationError extends Error {
  name = "ValidationError";
  constructor(field, message) { super(message); this.field = field; }
}
class OrderLoadError extends Error { name = "OrderLoadError"; }
function validateOrder(o) {
  if (o.phone?.length !== 11) throw new ValidationError("phone", "ফোন নম্বর ১১ অঙ্কের হতে হবে");
  if (!(o.qty >= 1)) throw new ValidationError("qty", "পরিমাণ অন্তত ১টি");
}
function loadOrder(text) {
  try { return JSON.parse(text); }
  catch (err) { throw new OrderLoadError("অর্ডার পড়া যায়নি", { cause: err }); }
}
function handle(job) {
  try { job(); console.log("ঠিক আছে"); }
  catch (err) {
    if (err instanceof ValidationError) console.log(err.name, err.field, "→", err.message);
    else if (err instanceof OrderLoadError) console.log(err.name, "→", err.message, "| কারণ:", err.cause.name);
    else throw err;                       // অচেনা — যে বোঝে, তার কাছে যাক
  }
}
handle(() => validateOrder({ phone: "0171", qty: 2 }));
handle(() => validateOrder({ phone: "01711223344" }));
handle(() => loadOrder('{"qty": 2,}'));

প্রথম লাইনে ValidationError phone → ফোন নম্বর ১১ অঙ্কের হতে হবে — ফর্মের কোড err.field দেখে ঠিক ফোনের ঘরের নিচে লাল বার্তা বসাতে পারে, বার্তা পার্স করতে হয় না। দ্বিতীয় লাইনে একই class, কিন্তু qty ঘর — এখানে qty দেওয়াই হয়নি। তৃতীয় লাইনে OrderLoadError → অর্ডার পড়া যায়নি | কারণ: SyntaxError — শেষে বাড়তি কমার জন্য JSON.parse-এর আসল error (Expected double-quoted property name in JSON at position 10…) err.cause-এ সংরক্ষিত। সব ঘর বৈধ হলে ছাপা হতো ঠিক আছে. খেয়াল করুন o.qty >= 1-এর চারপাশের !( … ): qty না থাকলে বা NaN হলে তুলনাটি false, তাই সেটিও ধরা পড়ে — o.qty < 1 লিখলে undefined < 1 false দিয়ে ভুল ইনপুট পার হয়ে যেত। আর else throw err নিশ্চিত করে যে কোনো অচেনা বাগ এখানে চাপা পড়বে না।

instanceof-এর একটি সীমা আছে: iframe বা অন্য window-এর (অন্য “realm”) কোড থেকে আসা error-এর Error class আলাদা, তাই সেখানে instanceof false দিতে পারে। এমন সীমানা পার হওয়া জায়গায় অনেকে err.name-ও দেখেন। আর minify করা কোডে class-এর নাম বদলে যেতে পারে — this.name তাই সবসময় স্পষ্ট string-এ লিখুন, this.constructor.name দিয়ে নয়।

৬ · exception নাকি result object? যাচাই, guard clause আর retry

এখন একটি নকশার প্রশ্ন। ব্যালান্স কম হলে withdraw কি exception ছুড়বে, নাকি { ok: false, error } ফেরত দেবে? দুটোই বৈধ — প্রশ্ন হলো ব্যর্থতাটা কতটা “স্বাভাবিক”।

💥 Exception ছুড়ুন যখন…

ডাকার কোডের কাছে এটি সত্যিই ব্যতিক্রম: বাগ, ভাঙা অনুমান, যে ব্যর্থতার পর এই স্তরে আর কিছু করার নেই। এটি অনেক স্তর পেরিয়ে একবারে ওপরে পৌঁছায়, মাঝের কোড পরিষ্কার থাকে।

programmer error, ভাঙা config, অপ্রত্যাশিত সার্ভার উত্তর

📦 Result object ফেরত দিন যখন…

ব্যর্থতা প্রত্যাশিত আর ঠিক পরের লাইনেই সামলানো হবে: ফর্মের ভুল ইনপুট, কম ব্যালান্স, খোঁজে কিছু না পাওয়া। Function-এর ফেরত টাইপেই দুই পথ লেখা থাকে, ডাকার কোড ভুলেও এড়াতে পারে না।

validation, ব্যবসার নিয়ম, “পাওয়া যায়নি”

অনেক কোডবেসে আরেকটি মাঝামাঝি পথ চলে: function-এর ভেতরে guard clause দিয়ে throw, আর একেবারে সীমানায় (বাটনের click handler, API route) একটি try/catch, যা error-কে ব্যবহারকারীর বার্তায় রূপান্তর করে। Go আর Rust-এর মতো ভাষা পুরো ভাষাটাই result-ধাঁচে বানিয়েছে (ভাবনার প্রশ্ন ২)। JavaScript-এ দুটোই আছে — মিশিয়ে নয়, একটি স্তরে একটি নিয়ম রাখুন।

Retry — কখন আবার চেষ্টা করা যুক্তিসংগত

Operational error-এর একটি বড় অংশ সাময়িক: নেটওয়ার্ক এক মুহূর্তের জন্য গেল, সার্ভার ব্যস্ত। এখানে সামলানো মানে আবার চেষ্টা। নিচে একটি synchronous অনুকরণ — আসল async retry, অপেক্ষার সময় বাড়ানো (backoff) আর বাতিল করা পর্ব ৩৬-এ:

function makeFlaky(failTimes) {          // প্রথম কয়েকবার "নেটওয়ার্ক" ব্যর্থ হয়
  let calls = 0;
  return () => {
    calls++;
    if (calls <= failTimes) throw new Error(`সংযোগ বিচ্ছিন্ন (চেষ্টা ${calls})`);
    return "SMS পাঠানো হয়েছে";
  };
}
function retry(fn, times) {
  let lastErr;
  for (let i = 1; i <= times; i++) {
    try { return fn(); }
    catch (err) { lastErr = err; console.log("ব্যর্থ:", err.message); }
  }
  throw new Error(`${times} বার চেষ্টাতেও হয়নি`, { cause: lastErr });
}
console.log(retry(makeFlaky(2), 3));
try { retry(makeFlaky(5), 2); }
catch (err) { console.log(err.message, "| শেষ কারণ:", err.cause.message); }

const withdraw = (balance, amount) => amount > balance
  ? { ok: false, error: `ব্যালান্স কম: আছে ${balance} টাকা` }
  : { ok: true, balance: balance - amount };
console.log(withdraw(500, 200), withdraw(500, 900));

প্রথম retry-এ দুবার ব্যর্থতা ছাপা হয় (ব্যর্থ: সংযোগ বিচ্ছিন্ন (চেষ্টা 1), তারপর (চেষ্টা 2)), তৃতীয়বার SMS পাঠানো হয়েছে — catch-এর ভেতরে থেকেও লুপ চলতে থাকে, আর try-এর ভেতরের return সফল হওয়ামাত্র পুরো function থেকে বের করে আনে। দ্বিতীয়টিতে দুবারই ব্যর্থ, তাই শেষে নতুন error: 2 বার চেষ্টাতেও হয়নি | শেষ কারণ: সংযোগ বিচ্ছিন্ন (চেষ্টা 2) — cause-এ শেষ ব্যর্থতার আসল বার্তা। শেষ লাইনে withdraw কোনো exception ছোড়ে না: { ok: true, balance: 300 } আর { ok: false, error: 'ব্যালান্স কম: আছে 500 টাকা' } — কম ব্যালান্স প্রত্যাশিত ঘটনা, তাই ফলের অংশ। বাস্তবে retry করবেন শুধু সাময়িক আর বারবার করলে ক্ষতি নেই এমন কাজে — ভুল পাসওয়ার্ড বা ValidationError-এ আবার চেষ্টা অর্থহীন, আর টাকা কাটার মতো কাজ দুবার হয়ে গেলে বিপদ।

৭ · যে error try/catch ধরতে পারে না — callback, timer, promise আর global handler

try/catch শুধু সেই error ধরে, যা try block চলার সময়, একই call stack-এ ছোড়া হয়। একটি পরিচিত ফাঁদ দেখুন:

try {
  setTimeout(() => {
    throw new Error("টাইমারের ভেতরে ভুল");
  }, 1000);
  console.log("টাইমার বসানো হলো");
} catch (err) {
  console.log("ধরা পড়ল?", err.message);   // কখনো চলবে না
}
// প্রত্যাশিত আউটপুট: টাইমার বসানো হলো
// এক সেকেন্ড পরে console-এ: Uncaught Error: টাইমারের ভেতরে ভুল

কেন? setTimeout শুধু callback-টি জমা দেয় আর সাথে সাথে ফিরে আসে। try block শেষ, catch-এর পাহারাও শেষ, stack খালি। এক সেকেন্ড পরে event loop (পর্ব ৩১) callback-টি একটি নতুন, খালি call stack-এ চালায় — সেখানে কোনো try নেই, তাই error uncaught. পর্ব ৩২-এর সব callback, event listener আর timer-এর জন্য নিয়ম একটাই: try/catch লাগবে callback-এর ভেতরে, বাইরে নয়।

Promise আর async/await — এক ঝলক

Asynchronous কাজের ভুল আসে ব্যর্থ (rejected) promise হয়ে। async function-এর ভেতরে await করা promise ব্যর্থ হলে সেটি আবার সাধারণ throw-এর মতো আচরণ করে — চেনা try/catch দিয়েই ধরা যায়। কিন্তু কোনো promise-এর ব্যর্থতা কেউ না সামলালে হয় unhandled rejection: ব্রাউজারে console-এ Uncaught (in promise) আর window-এ unhandledrejection event, Node.js 15 থেকে ডিফল্টে পুরো process বন্ধ। Promise, .catch(), Promise.allSettled আর AggregateError-এর বিস্তারিত পর্ব ৩৩–৩৪-এ।

শেষ জাল — global error handler

সব সাবধানতার পরেও কোনো error ফাঁক গলে বেরোবে। তার জন্য একেবারে ওপরে একটি global error handler রাখা হয় — কাজ রিপোর্ট করা, সামলানো নয়:

// ব্রাউজার: কেউ ধরেনি এমন error আর promise-ব্যর্থতা
window.addEventListener("error", (event) => {
  reportToServer({ message: event.message, stack: event.error?.stack });
});
window.addEventListener("unhandledrejection", (event) => {
  reportToServer({ message: String(event.reason) });
});

// Node.js: log করে বন্ধ হন — চালিয়ে যাবেন না
process.on("uncaughtException", (err) => {
  console.error("অপ্রত্যাশিত ভুল:", err);
  process.exit(1);                    // pm2/systemd/Docker নতুন করে চালু করবে
});

Node.js-এর নিয়মটি গুরুত্বপূর্ণ। uncaughtException-এ পৌঁছানো মানে প্রোগ্রাম এমন অবস্থায় আছে যা কেউ পরিকল্পনা করেনি — হয়তো অর্ধেক লেখা ডেটা, খোলা থাকা সংযোগ। Node.js-এর নিজের documentation-ও বলে: এখানে শুধু সাথে সাথে করা যায় এমন পরিষ্কার আর log, তারপর বন্ধ হওয়া; “কিছুই হয়নি” ভেবে চালিয়ে গেলে পরের ভুলগুলো আরও অদ্ভুত হয়। পুরোনো ধাঁচে লেখা window.onerror = (message, source, line, col, error) => …-ও একই কাজ করে। আর অন্য ডোমেইন থেকে আনা script-এর error ব্রাউজার নিরাপত্তার কারণে শুধু "Script error." হিসেবে দেখায় — বিস্তারিত পেতে সেই script-এ CORS লাগে (পর্ব ৪৩)।

৮ · ব্যবহারকারীর বার্তা, log আর debugging

একই error-এর দুজন পাঠক — ব্যবহারকারী আর ডেভেলপার। দুজনের দরকার সম্পূর্ণ আলাদা।

ব্যবহারকারীকেLog বা monitoring-এ
কী লিখবেনকী হলো আর এখন কী করবেন: “পেমেন্ট সম্পন্ন হয়নি। আপনার টাকা কাটা হয়নি, আবার চেষ্টা করুন।”name, message, stack, cause-এর শিকল, অর্ডার ID, সময়, ব্রাউজার/সংস্করণ
কখনো নয়stack trace, SQL query, ফাইলের path, সার্ভারের ভেতরের নাম, কাঁচা err.messageপাসওয়ার্ড, OTP, token, পূর্ণ কার্ড নম্বর, NID

ব্যবহারকারীর পর্দায় stack trace দেখানো দুইভাবে ক্ষতিকর। সাধারণ মানুষের কাছে এটি ভয়ের আর অর্থহীন। আর আক্রমণকারীর কাছে এটি মানচিত্র — কোন framework, কোন সংস্করণ, কোন ফোল্ডারে কী আছে। একই কারণে log-এ গোপন তথ্য লিখবেন না: log ফাইল প্রায়ই অনেকে দেখেন, তৃতীয় পক্ষের সার্ভিসে যায়, বছরের পর বছর থাকে। Error-এর বার্তায় ব্যবহারকারীর দেওয়া ইনপুট বসালে সেটি পাতায় দেখানোর সময় innerHTML নয়, textContent ব্যবহার করুন (পর্ব ৩৭, ৪৩)।

console.assert — নিজের অনুমান যাচাই

কোডের কোথাও যদি ভাবেন “এখানে মোট দাম কখনো ঋণাত্মক হবে না”, সেই অনুমানটি লিখে রাখুন: console.assert(total >= 0, "মোট দাম ঋণাত্মক!", total). শর্ত মিথ্যা হলে console-এ Assertion failed: মোট দাম ঋণাত্মক! -50 দেখাবে। মনে রাখবেন, console.assert ব্রাউজার বা Node.js-এ exception ছোড়ে না, প্রোগ্রামও থামায় না — এটি উন্নয়নের সময়ের সতর্কসংকেত। সত্যিকারের নিয়ম রক্ষা করতে if (…) throw … লিখুন; এটাই fail fast নীতি — ভুল অবস্থা যত কাছে ধরা পড়ে, খুঁজে পাওয়া তত সহজ (ভাবনার প্রশ্ন ৫)।

Debugging — stack trace থেকে কারণে

  • Stack trace পড়ুন নিচ থেকে নিজের কোড খুঁজে: console-এ error-এর পাশের ফাইল-লাইন লিংকে ক্লিক করলে DevTools ঠিক সেই লাইনে নিয়ে যায়। cause থাকলে শিকলের শেষ মাথা পর্যন্ত যান — আসল কারণ প্রায়ই সেখানে।
  • Pause on exceptions: DevTools-এর Sources প্যানেলের Breakpoints অংশে “Pause on uncaught exceptions” চালু করলে error ছোড়ার ঠিক মুহূর্তে debugger থামে — তখন সব variable-এর মান আর পুরো call stack চোখের সামনে। “Pause on caught exceptions” চালু করলে catch করা error-এও থামে — কোনো catch কিছু চাপা দিচ্ছে কি না ধরার সেরা উপায়।
  • Production-এ error monitoring: ব্যবহারকারীর ব্রাউজারে কী ঘটছে, আপনি দেখতে পান না। Sentry-র মতো সার্ভিস global handler-এর মতো error সংগ্রহ করে, একই ধরনের error একসাথে গুনে দেখায় আর source map দিয়ে minify করা কোডকে মূল লাইনে ফিরিয়ে আনে। ব্যবহারকারীর গোপন তথ্য যেন সেখানে না যায়, সেই সেটিংসও দেখে নিন।
C/C++/Python/Java জানলে

C-তে exception নেই — function ভুল জানায় ফেরত-মান (-1, NULL) আর errno দিয়ে (C-র error handling). C++, Java আর Python-এর try/catch (Python-এ try/except) মূল ধারণায় JavaScript-এর মতোই, তবে সেখানে catch (IOException e) বা except ValueError লিখে টাইপ ধরে আলাদা catch লেখা যায়; JavaScript-এ একটিই catch, ভেতরে instanceof দিয়ে বাছাই। Java-র checked exception (compile-এর সময় “ধরো নয়তো ঘোষণা করো”) JavaScript-এ নেই, আর Java-র getCause() বা Python-এর raise … from err-এর জায়গায় এখানে { cause }. বিস্তারিত: Java-র exception, Python-এর exception, C++-এর exception.

পরীক্ষা ও ইন্টারভিউতে
  • function f() { try { return 1; } finally { return 2; } } — f() কী দেয়? — 2; finally-র return try-এর ফেরত মান বদলে দেয় (আর চলমান error-ও মুছে দেয়)।
  • try-এর ভেতরে setTimeout-এর callback-এ throw হলে catch ধরবে? — না; callback পরে নতুন call stack-এ চলে, তখন try শেষ। try/catch লাগবে callback-এর ভেতরে।
  • string না ছুড়ে Error object কেন? — Error-এ stack, name আর message থাকে, instanceof দিয়ে বাছাই করা যায়; string-এ কোথায় ভুল হলো তার চিহ্ন নেই।
  • TypeError আর ReferenceError-এর পার্থক্য? — ReferenceError: নামটিই নেই (totl is not defined); TypeError: নাম আছে কিন্তু মানটি দিয়ে কাজটি করা যায় না (undefined-এর property পড়া)।
  • Error cause কী? — ES2022-এর new Error(msg, { cause: err }); প্রসঙ্গসহ নতুন error বানানো যায়, আসলটি err.cause-এ থেকে যায়।

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

AI-কে কোনো function লিখতে বললে সে প্রায়ই চারপাশে একটি try/catch বসিয়ে দেয় — দেখতে “নিরাপদ” লাগে। কিন্তু error handling-এ ভুল কোড চালালে কিছুই ভাঙে না; ভাঙে পরে, production-এ, যখন একটি চাপা পড়া error তিন দিন ধরে নীরবে ভুল ডেটা লিখে যায়। তাই AI-এর লেখা catch block-গুলো আলাদা করে পড়ুন।

AI যা প্রায়ই লেখেসমস্যাযা চাইবেন
catch (e) {} বা শুধু console.log(e)error গিলে ফেলা — প্রোগ্রাম ভুল অবস্থায় চলতে থাকে, কেউ জানে নাসামলান, নয়তো প্রসঙ্গসহ আবার ছুড়ুন
পুরো function try-এ মুড়ে catch { return null; }বাগ, নেটওয়ার্ক সমস্যা, “পাওয়া যায়নি” — সব এক null; পরে কোথাও Cannot read properties of nullনির্দিষ্ট error ধরা, বাকিগুলো rethrow; প্রত্যাশিত ব্যর্থতায় result object
throw "Invalid input"stack নেই, instanceof চলে নাthrow new ValidationError(...)
throw new Error("Failed") catch-এর ভেতরে, cause ছাড়াআসল কারণ হারায়{ cause: err } যোগ করা
res.send(err.stack) বা alert(err.message)ব্যবহারকারীর কাছে ভেতরের তথ্য ফাঁসব্যবহারকারীকে সাধারণ বার্তা, বিস্তারিত শুধু log-এ
err.message.includes("not found") দিয়ে সিদ্ধান্তবার্তা engine/সংস্করণভেদে বদলায়instanceof, name বা code

AI-কে error-সংক্রান্ত কোড লিখতে বললে নিয়মগুলো আগেই দিয়ে দিন, আর ব্যর্থতার পরীক্ষাও চেয়ে নিন:

আমি JavaScript-এ একটি অনলাইন দোকানের placeOrder(form) ফাংশন লিখছি। নিচের নিয়ম মেনে কোড লিখে দাও: ১. ফর্ম যাচাইয়ের ভুলে ValidationError (extends Error) ছুড়বে, যাতে name = "ValidationError" আর কোন ঘর ভুল তা field-এ থাকবে। ২. সার্ভারের উত্তর পড়ার ভুল OrderSaveError দিয়ে মুড়বে, আর আসল error অবশ্যই { cause: err }-এ রাখবে। ৩. কোথাও খালি catch, "catch করে null ফেরত", বা string throw থাকবে না; যে error চেনো না, সেটা আবার throw করবে। ৪. finally-তে শুধু পরিষ্কারের কাজ (বাটন আবার চালু), সেখানে return নয়। ৫. ব্যবহারকারীকে শুধু বাংলায় বোধগম্য বার্তা দেখাবে; stack trace বা err.message সরাসরি দেখাবে না, বিস্তারিত logError()-এ পাঠাবে, কিন্তু ফোন নম্বর বা পাসওয়ার্ড log করবে না। ৬. err.message-এর লেখা মিলিয়ে কোনো সিদ্ধান্ত নেবে না — instanceof বা name দেখবে। শেষে ৫টি পরীক্ষার কেস দাও: ফোন নম্বর ছোট, পরিমাণ ০, সার্ভার HTML পাঠাল, সার্ভার ঠিক উত্তর দিল, আর একটি অপ্রত্যাশিত TypeError — প্রতিটিতে ব্যবহারকারী কী দেখবে আর log-এ কী যাবে।

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

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

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

প্র ০১exception-এর ধারণা কোথা থেকে এলো? PL/I, CLU আর Lisp থেকে C++ আর Java-র checked exception পর্যন্ত যাত্রায় কী শেখার আছে?

প্রথম যুগের প্রোগ্রামে ভুল জানানোর উপায় ছিল একটাই — ফেরত-মান। C-র fopen ব্যর্থ হলে NULL দেয়, আর কারণ লেখে global errno-তে। এর সমস্যা দুটি: প্রতিটি ডাকের পর পরীক্ষা করতে হয় (ভুলে গেলে ভুল চুপচাপ এগিয়ে যায়), আর আসল কাজের কোড ভুল-পরীক্ষার ভিড়ে হারিয়ে যায়।

১৯৬০-এর দশকে IBM-এর PL/I ভাষা “ON condition” আনে — যেমন ON ZERODIVIDE লিখে আগেই বলে রাখা যেত, শূন্য দিয়ে ভাগ হলে কোন কোড চলবে। তবে এর নিয়ম ছিল জটিল আর dynamic, কোথা থেকে কোন handler চলবে বোঝা কঠিন। ১৯৭৫ সালে John Goodenough-এর একটি বিখ্যাত গবেষণাপত্র exception handling-এর সমস্যা আর নকশার প্রশ্নগুলো গুছিয়ে তোলে। MIT-তে Barbara Liskov-এর দল CLU ভাষায় (১৯৭০-এর দশক) একটি পরিষ্কার মডেল দেয়: function তার সম্ভাব্য exception ঘোষণা করে, আর ডাকার জায়গায় সেগুলো ধরা হয় — অনেকটা আজকের ধাঁচ। Lisp-এর জগতে catch/throw এর আগেই ছিল, আর পরে Common Lisp-এর “condition system” আরও এক ধাপ এগোয়: error ঘটলে stack সাথে সাথে খোলে না, ওপরের কোড ঠিক করতে পারে নিচে কোন “restart” পথে চলবে। আজকের মূলধারার ভাষাগুলো এই শক্তিশালী মডেল নেয়নি — সরল “unwind করে catch” মডেলই জিতেছে।

C++-এ Bjarne Stroustrup আর Andrew Koenig ১৯৯০ সালের দিকে exception নকশা করেন, আর ১৯৯৮-এর মানদণ্ডে সেটি স্থায়ী হয়। Java (১৯৯৫) CLU-র ধারণাকে আরও কড়া করে: “checked exception” — কিছু exception হয় ধরতে হবে, নয়তো method-এর স্বাক্ষরে throws লিখতে হবে, না হলে compile-ই হবে না। উদ্দেশ্য মহৎ ছিল: কোনো ব্যর্থতা যেন ভুলে বাদ না পড়ে। কিন্তু বাস্তবে অনেকে ঝামেলা এড়াতে খালি catch লিখতে শুরু করে — ঠিক যে ভুল ঠেকাতে নিয়মটি এসেছিল। C# (২০০০) তাই সচেতনভাবে checked exception নেয়নি; Anders Hejlsberg ২০০৩ সালের একটি সাক্ষাৎকারে versioning আর scalability-র সমস্যা দেখিয়ে এর ব্যাখ্যা দেন। পরের Kotlin আর Scala-ও একই পথে।

JavaScript-এর প্রথম সংস্করণে (১৯৯৫) exception ছিলই না; ভুলে script শুধু থেমে যেত। ES3 (১৯৯৯) Java-র চেনা try/catch/finally আর throw আনে, তবে টাইপ-ভিত্তিক একাধিক catch ছাড়া আর checked exception ছাড়া।

মূল উপলব্ধি: exception-এর ইতিহাস হলো দুটি লক্ষ্যের টানাপোড়েন — ভুল যেন বাদ না পড়ে, আবার মূল কোড যেন ভুল-পরীক্ষায় ডুবে না যায়। কোনো ভাষাই নিখুঁত উত্তর পায়নি; নিয়ম যত কড়া, মানুষ তত ফাঁকি খোঁজে।

প্র ০২JavaScript-এ checked exception নেই কেন? আর Go ও Rust কেন exception বাদ দিয়ে error-কে সাধারণ ফেরত-মান বানাল?

Checked exception কাজ করতে হলে compiler-কে জানতে হয় কোন function কোন exception ছুড়তে পারে, আর সেটা মিলিয়ে দেখতে হয় প্রতিটি ডাকের জায়গায়। এর জন্য লাগে একটি static type system আর compile ধাপ। JavaScript dynamic টাইপের ভাষা — চালানোর আগে কেউ টাইপ পরীক্ষা করে না, যেকোনো মান ছোড়া যায়, যেকোনো function যেকোনো সময় বদলানো যায়। এখানে “এই function শুধু X ছোড়ে” ঘোষণা মিলিয়ে দেখার উপায়ই নেই। তার ওপর Java-র অভিজ্ঞতা দেখিয়েছিল যে checked exception-এর খরচও কম নয়।

মজার ব্যাপার, TypeScript-ও (পর্ব ৪৭) এটা যোগ করেনি — function-এর টাইপে “কী ছুড়তে পারে” লেখার কোনো ব্যবস্থা নেই। বরং TypeScript 4.4 থেকে strict মোডে catch-এর variable-এর টাইপ unknown — অর্থাৎ “যা খুশি আসতে পারে, ব্যবহারের আগে পরীক্ষা করো”, কারণ JavaScript-এ যেকোনো মান ছোড়া যায়।

Go (২০০৯) আরেক পথে গেল। সেখানে সাধারণ ব্যর্থতা ফেরত আসে শেষ ফেরত-মান হিসেবে: file, err := os.Open(name), তারপর if err != nil { return err }. Rob Pike-এর ২০১৫ সালের “Errors are values” লেখা এই দর্শনের পরিচিত ব্যাখ্যা: error সাধারণ মান, তাই অন্য মানের মতোই প্রোগ্রাম দিয়ে সামলানো যায়। সমালোচনাও আছে — কোডে if err != nil-এর ভিড়। সত্যিকারের ব্যতিক্রমী অবস্থার জন্য Go-তে panic/recover আছে, তবে সেটা সাধারণ ব্যর্থতার জন্য নয়।

Rust এই ধারণাকে টাইপ সিস্টেমে বেঁধে দিয়েছে: ব্যর্থ হতে পারে এমন function ফেরত দেয় Result<T, E> — হয় Ok(value), নয় Err(error). মান বের করতে হলে দুই পথই সামলাতে হয়, আর compiler নিশ্চিত করে কেউ ভুলে এড়াচ্ছে না। ? operator এক অক্ষরে “ব্যর্থ হলে error ওপরে পাঠাও” লিখতে দেয় — exception-এর সুবিধা, কিন্তু প্রতিটি সম্ভাব্য ব্যর্থতার জায়গা কোডে চোখে দেখা যায়। আর বাগের মতো অপ্রত্যাশিত অবস্থার জন্য আছে panic!.

JavaScript-এ এই ধাঁচটি হাতে বানানো যায় — অংশ ৬-এর result object ঠিক তাই, আর কিছু library এটিকে আরও গুছিয়ে দেয়। কিন্তু ভাষা নিজে বাধ্য করে না, তাই দলের নিয়মই ভরসা।

মূল উপলব্ধি: exception “অদৃশ্য নিয়ন্ত্রণ-প্রবাহ” — যেকোনো লাইন থেকে বের হওয়া যায়; error value “দৃশ্যমান” — প্রতিটি ব্যর্থতা কোডে লেখা থাকে। নতুন ভাষাগুলো দৃশ্যমানতার দিকে ঝুঁকেছে; JavaScript-এ দুটোই আছে, আর কোথায় কোনটি, সেই বিচার আপনার।

প্র ০৩সব browser-এ err.stack আছে, তবু এটি মানদণ্ডের অংশ নয় কেন? আর V8-এর Error.captureStackTrace কী কাজ করে?

ECMAScript মানদণ্ড Error object-এর জন্য বলে name, message আর (ES2022 থেকে) cause-এর কথা। stack কোথাও নেই। অথচ প্রতিটি বড় engine এটি দেয় — কারণ stack ছাড়া debugging প্রায় অসম্ভব। ব্যাপারটা ঘটেছে ঐতিহাসিকভাবে: Netscape/Mozilla আর পরে V8 নিজের নিজের মতো stack যোগ করেছিল, মানদণ্ড আসার আগেই। ফলে আকার আলাদা: V8 লেখে at c (app.js:2:27), Firefox লেখে c@app.js:2:27, Safari-র আকারও কাছাকাছি কিন্তু হুবহু নয়। কোন frame দেখানো হবে, কতগুলো, async ডাকের ধারাবাহিকতা দেখানো হবে কি না — সবই engine-এর সিদ্ধান্ত। V8 ডিফল্টে ১০টি frame রাখে (Error.stackTraceLimit).

মানদণ্ড করা কঠিন কেন? কয়েকটি কারণ। প্রথমত, পুরোনো ওয়েবসাইট এই আলাদা আকারগুলো পার্স করার কোডে ভরা — আকার বদলালে সেগুলো ভাঙবে। দ্বিতীয়ত, stack দেখায় কোন function কোন function-কে ডেকেছে, যা নিরাপত্তা আর optimization-এর দিক থেকে স্পর্শকাতর — engine যখন function inline করে (পর্ব ০৫-এর JIT), তখন “আসল” stack কী, সেই প্রশ্নও ওঠে। তৃতীয়ত, কোনো তথ্য অন্য origin-এর script থেকে আসছে কি না, সেটাও বিবেচনায় রাখতে হয়। TC39-এ stack-কে মানদণ্ডে আনার প্রস্তাব নিয়ে আলোচনা হয়েছে, কিন্তু এখনো তা ভাষার অংশ হয়নি।

V8 এই ফাঁকে কিছু নিজস্ব API দিয়েছে, যা Node.js-জগতে খুব ব্যবহৃত। Error.captureStackTrace(obj, fn) যেকোনো object-এ একটি stack property বসিয়ে দেয়, আর দ্বিতীয় argument দিলে সেই function আর তার ওপরের frame-গুলো বাদ দেয়। Library লেখকেরা এটা ব্যবহার করেন, যাতে stack-এ library-র ভেতরের helper না দেখিয়ে ব্যবহারকারীর কোড থেকে শুরু হয়। আছে Error.prepareStackTrace নামে একটি hook-ও, যা দিয়ে stack-এর আকার নিজের মতো বানানো যায় — source map-ভিত্তিক টুল একসময় এর ওপর নির্ভর করত। কিন্তু এগুলো V8-কেন্দ্রিক; ব্রাউজারভেদে বহনযোগ্য কোডে এদের ওপর ভরসা করা যায় না।

ব্যবহারিক শিক্ষা: stack মানুষের পড়ার আর monitoring টুলে পাঠানোর জন্য, প্রোগ্রামের যুক্তির জন্য নয়। কোডে সিদ্ধান্ত নিন name, instanceof আর নিজের field দিয়ে।

মূল উপলব্ধি: ওয়েবের অনেক “মানদণ্ড” আসলে আগে বাস্তবায়ন, পরে দলিল। একবার কোটি সাইট কোনো আচরণের ওপর নির্ভর করলে সেটা আর বদলানো যায় না — তাই stack আজও সবখানে আছে, অথচ কোথাও লেখা নেই।

প্র ০৪ES2022-এর আগে JavaScript-এ error-এর শিকল বানানো হতো কীভাবে? cause প্রস্তাব কোন সমস্যা মেটাল?

বড় অ্যাপে error প্রায় সবসময় কয়েক স্তর পেরোয়: ডেটাবেস driver-এর socket error → repository-র “ব্যবহারকারী আনা গেল না” → service-এর “অর্ডার তৈরি হয়নি” → API-র ৫০০ উত্তর। প্রতিটি স্তর নিজের প্রসঙ্গ জানে, আর ভালো log-এ পুরো শিকলটা থাকা দরকার — ওপরের অর্থ আর নিচের আসল কারণ, দুটোই।

অন্য ভাষা এটা অনেক আগে সমাধান করেছিল। Java 1.4 (২০০২) Throwable-এ “chained exception” আনে — new OrderException("…", e), আর stack trace-এ “Caused by:” অংশ। C#-এ আছে InnerException. Python 3 PEP 3134 দিয়ে raise NewError(...) from err আর __cause__ আনে। JavaScript-এ কিছুই ছিল না। ফলে প্রতিটি library নিজের নিয়ম বানাত: কেউ err.inner, কেউ err.originalError, কেউ err.cause; Node.js-জগতে Joyent-এর verror package জনপ্রিয় হয়েছিল এই কাজেই। Error monitoring টুল আর DevTools জানত না কোন property-তে আসল error আছে, তাই সাধারণভাবে দেখাতে পারত না। আর অনেকে সহজ পথে নতুন error-এর বার্তায় পুরোনো বার্তা জুড়ে দিত — আসল stack হারিয়ে।

TC39-এ Chengzhong Wu-র উত্থাপিত “Error Cause” প্রস্তাব সমাধানটা ইচ্ছা করে ছোট রেখেছে: Error constructor-এর দ্বিতীয় argument একটি options object, তাতে cause দিলে নতুন error-এ একই নামের একটি নিজস্ব property বসে। কোনো নতুন class নেই, কোনো স্বয়ংক্রিয় বার্তা-জোড়া নেই। প্রস্তাবটি ২০২১ সালে চূড়ান্ত পর্যায়ে পৌঁছে ES2022-এ যুক্ত হয়, আর সব built-in error constructor (TypeError, AggregateError…) একই option নেয়। মানদণ্ডিত নাম থাকায় টুলগুলো এখন এটা চিনতে পারে — Node.js-এর console.log নিজেই [cause] অংশ ছাপে।

একটি সূক্ষ্ম নকশা-সিদ্ধান্ত: cause যেকোনো মান হতে পারে, শুধু Error নয় — কারণ JavaScript-এ যেকোনো মান ছোড়া যায়, আর মোড়ানোর সময় সেটাই আসবে। আর cause না দিলে property-টি তৈরিই হয় না, তাই "cause" in err দিয়ে আলাদা করা যায় “cause নেই” আর “cause হলো undefined”.

মূল উপলব্ধি: একটি ছোট মানদণ্ডিত নামও বড় কাজ করে — কারণ তখন পুরো ইকোসিস্টেম (library, টুল, monitoring) একই ভাষায় কথা বলে। cause-এর আসল মূল্য তার প্রযুক্তিতে নয়, ঐকমত্যে।

প্র ০৫Jim Shore-এর “Fail Fast” (২০০৪) বলে ভুল দেখামাত্র থামুন; defensive programming বলে সব কিছুর জন্য প্রস্তুত থাকুন। দুটির মধ্যে বিরোধ কোথায়, আর মিল কোথায়?

২০০৪ সালে IEEE Software পত্রিকায় Jim Shore “Fail Fast” নামে একটি ছোট প্রবন্ধ লেখেন। মূল কথা: ভুল অবস্থা ধরা পড়ামাত্র প্রোগ্রাম জোরে আর স্পষ্টভাবে ব্যর্থ হোক। শুনতে উল্টো মনে হয় — ব্যর্থতা তো খারাপ! কিন্তু যুক্তিটা হলো, যে প্রোগ্রাম ভুল ডেটা দেখেও “মানিয়ে নিয়ে” চলে, তার বাগ দেখা দেয় অনেক পরে, অন্য জায়গায়, অন্য রূপে। একটি ফাঁকা config মান undefined হয়ে তিনটি function পেরিয়ে শেষে NaN টাকার হিসাব — কোথা থেকে শুরু, খুঁজতে ঘণ্টা লাগে। শুরুতেই throw new Error("PAYMENT_API_URL সেট করা নেই") হলে খুঁজতেই হয় না।

Defensive programming-এর ঐতিহ্য পুরোনো — ভুল ইনপুট, অপ্রত্যাশিত অবস্থা আগে থেকে ভেবে কোডে সুরক্ষা রাখা। সমস্যা হয় যখন “সুরক্ষা” মানে হয়ে দাঁড়ায় চুপচাপ মানিয়ে নেওয়া: amount = amount || 0, items ?? [], সবখানে try { … } catch { return null }. এতে প্রোগ্রাম ক্র্যাশ করে না ঠিকই, কিন্তু ভুল লুকিয়ে যায় — আর লুকানো ভুল ক্র্যাশের চেয়েও দামি, কারণ সেটা ভুল ডেটা লিখে যায়।

আসলে দুটি নীতির বিরোধ কম, মিল বেশি — তফাত হলো ভুলটা কোথা থেকে আসছে। বাইরের ভুলে (ব্যবহারকারীর ইনপুট, নেটওয়ার্ক, অন্য সার্ভিস) defensive হোন: যাচাই করুন, স্পষ্ট বার্তা দিন, আবার চেষ্টা করুন — এগুলো operational error. নিজের কোডের ভুলে (ভাঙা অনুমান, programmer error) fail fast: assert বা throw দিয়ে থামুন, যাতে বাগটা উন্নয়নের সময়েই চোখে পড়ে। Bertrand Meyer-এর “Design by Contract” (Eiffel ভাষা) একই ভাগ করে — function-এর precondition ভাঙা মানে ডাকার কোডের বাগ। Erlang-এর “let it crash” দর্শন এর আরও চরম রূপ: একটি ছোট process ভুলে মরে যাক, তার supervisor পরিষ্কার অবস্থায় নতুন করে চালু করবে — অংশ ৭-এর Node.js নিয়মের (log করে বন্ধ হও, process manager আবার চালাবে) পেছনেও একই চিন্তা।

আর “fail fast” মানে ব্যবহারকারীর সামনে ক্র্যাশ নয়। ভেতরের স্তর জোরে ব্যর্থ হয়, আর একেবারে বাইরের সীমানা (UI, API route) সেটা ধরে ভদ্র বার্তায় রূপান্তর করে, log পাঠায়।

মূল উপলব্ধি: সীমানায় সহনশীল, ভেতরে কঠোর। বাইরের পৃথিবীর অনিশ্চয়তার জন্য defensive যাচাই, আর নিজের কোডের ভাঙা অনুমানের জন্য জোরে, তাড়াতাড়ি ব্যর্থতা — যাতে বাগ লুকাতে না পারে।

অনুশীলন

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

  1. ক্রম অনুমান করুন: নিচের কোড কোন ক্রমে কী ছাপবে? function run() { try { console.log("A"); JSON.parse("{"); console.log("B"); return "C"; } catch (e) { console.log("D", e.name); return "E"; } finally { console.log("F"); } } console.log(run());

    A, তারপর D SyntaxError, তারপর F, শেষে E. JSON.parse("{") ব্যর্থ হয়, তাই B আর return "C" বাদ পড়ে। catch-এর return "E" ফেরত মান ঠিক করে, কিন্তু function বের হওয়ার আগে finally চলে — তাই F ছাপা হয় E-এর আগে। finally-তে return নেই বলে "E"-ই ফেরত আসে।

  2. ভুলের ধরন চিনুন: প্রতিটি কোন ধরনের — syntax error, programmer error, operational error নাকি logic bug? (ক) API-র উত্তর আসতে ৩০ সেকেন্ড, তারপর timeout (খ) user.adress.city — বানান ভুল (গ) ভ্যাট যোগের বদলে বিয়োগ হচ্ছে, কোনো error নেই (ঘ) ফর্মে বয়সের ঘরে “বিশ” লেখা (ঙ) const total = ; (চ) বন্ধ ফাইল আবার পড়ার চেষ্টা, কারণ আগে close() ডাকা হয়ে গেছে।

    (ক) operational — সাময়িক, retry বা স্পষ্ট বার্তা যুক্তিসংগত। (খ) programmer error — user.adress হলো undefined, তাই TypeError: Cannot read properties of undefined (reading 'city'); সমাধান বানান ঠিক করা, catch নয়। (গ) logic bug — কোনো error নেই, ধরবে শুধু টেস্ট। (ঘ) operational (ভুল ইনপুট) — প্রত্যাশিত, ValidationError বা result object দিয়ে ব্যবহারকারীকে জানান। (ঙ) syntax error — script চালানোর আগেই SyntaxError, কোনো লাইনই চলবে না। (চ) programmer error — নিজের কোডের ক্রমের ভুল; fail fast দিয়ে দ্রুত ধরা পড়াই ভালো।

  3. নিজের error class (কোড লিখুন): InsufficientFundsError বানান, যাতে balance আর amount field থাকে, name ঠিক থাকে, আর বার্তা হয় “আরও X টাকা দরকার”. তারপর একটি pay(wallet, amount) লিখুন যা কম ব্যালান্সে এটি ছোড়ে, আর ডাকার জায়গায় শুধু এই error ধরে বাকিগুলো rethrow করুন।

    একটি সমাধান: class InsufficientFundsError extends Error { constructor(balance, amount) { super(`আরও ${amount - balance} টাকা দরকার`); this.name = "InsufficientFundsError"; this.balance = balance; this.amount = amount; } }. তারপর function pay(wallet, amount) { if (amount > wallet.balance) throw new InsufficientFundsError(wallet.balance, amount); wallet.balance -= amount; }. ডাকার জায়গায়: try { pay({ balance: 300 }, 500); } catch (err) { if (err instanceof InsufficientFundsError) console.log(err.message); else throw err; } → আরও 200 টাকা দরকার. যাচাই করুন: err instanceof Error true কি না, আর this.name বাদ দিলে err.name কী হয় ("Error"). ভাবুন: এই ক্ষেত্রে result object কি আরও ভালো হতো? (যদি ডাকার কোড সবসময় ঠিক পরের লাইনেই সামলায়, তাহলে হ্যাঁ।)

  4. AI অডিট: যেকোনো AI-কে বলুন “JavaScript-এ একটি loadUserProfile(id) ফাংশন লেখো যা সার্ভার থেকে JSON আনে।” তারপর অংশ ৯-এর চেকলিস্ট দিয়ে উত্তরটি পরীক্ষা করুন। কোন কোন ভুল পেলেন, আর ঠিক করতে AI-কে কী বলবেন?

    সাধারণত দেখা যায়: (১) পুরো function একটি try-এ, আর catch (error) { console.error(error); return null; } — নেটওয়ার্ক বন্ধ, ৪০৪, ভাঙা JSON আর কোডের বাগ সব এক null, আর পরে ডাকার কোডে Cannot read properties of null. (২) response.ok পরীক্ষা নেই — ৫০০ উত্তরও “সফল” ধরে JSON পড়তে গিয়ে SyntaxError. (৩) মোড়ানো error-এ cause নেই। (৪) কখনো alert(error.message) — ভেতরের বার্তা সরাসরি ব্যবহারকারীকে। ঠিক করতে বলুন: “৪০৪ হলে { ok: false, reason: 'not-found' } ফেরত দাও, অন্য HTTP ভুলে আর ভাঙা JSON-এ ProfileLoadError ছোড়ো { cause } সহ, অচেনা error rethrow করো, আর ব্যবহারকারীর বার্তা আলাদা রাখো।” fetch আর HTTP-র বিস্তারিত পর্ব ৩৫-এ।

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

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

ক্রমএকটি নিচের function-এ error ছোড়া থেকে ওপরের catch-এ ধরা পর্যন্ত ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. throw new Error(...) — Error object তৈরি হয়, সেই মুহূর্তের stack রেকর্ড হয়
  2. বর্তমান function সাথে সাথে থামে, তার বাকি লাইন বাদ পড়ে
  3. Engine call stack-এ একটির পর একটি frame সরিয়ে try খোঁজে
  4. প্রথম যে try-এর ভেতরে ডাকটি ছিল, তার catch block error-টি পায় ও চলে
  5. catch শেষে প্রোগ্রাম try/catch-এর পরের লাইন থেকে স্বাভাবিকভাবে চলে

function f() { try { return 1; } finally { return 2; } } — f()-এর ফল কী?

  • 1
  • 2
  • [1, 2]
  • SyntaxError

const u = undefined; u.name; চালালে কোন error আসে?

  • ReferenceError
  • SyntaxError
  • TypeError
  • RangeError

একটি try block-এর ভেতরে setTimeout(() => { throw new Error("x"); }, 0) লেখা হলো। callback-এর error কি সেই try-এর catch ধরবে?

  • না — callback চলার সময় try শেষ হয়ে গেছে
  • হ্যাঁ — callback try-এর ভেতরে লেখা, তাই ধরবে
  • হ্যাঁ, কিন্তু শুধু বিলম্ব ০ হলে
  • callback-এ throw করাই নিষেধ, SyntaxError হয়

throw "ব্যালান্স কম"-এর বদলে throw new Error("ব্যালান্স কম") লেখার প্রধান কারণ কী?

  • string ছোড়া হলে SyntaxError হয়
  • catch string ধরতে পারে না
  • Error object দ্রুততর চলে
  • Error-এ stack, name, message থাকে আর instanceof দিয়ে বাছাই করা যায়

new Error("অর্ডার সেভ হয়নি", { cause: err })-এ cause কী কাজ করে?

  • err-এর বার্তা নতুন বার্তার শেষে জুড়ে দেয়
  • আসল error-কে নতুন error-এর cause property-তে রেখে দেয়
  • err-কে সাথে সাথে আবার ছুড়ে দেয়
  • নতুন error-এর stack মুছে err-এর stack বসায়

ফর্মে ভুল ফোন নম্বরের মতো প্রত্যাশিত ব্যর্থতা, যা ঠিক পরের লাইনেই সামলানো হবে — তার জন্য কোন নকশা সাধারণত সবচেয়ে উপযুক্ত?

  • পুরো কোড try-এ মুড়ে খালি catch {}
  • ভুল হলে চুপচাপ null ফেরত দেওয়া
  • { ok: false, error } ধাঁচের result object ফেরত দেওয়া
  • window-এর global error handler-এ সামলানো

শব্দকোষ

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

মনে রাখুন

  1. throw করলে বর্তমান function থামে, আর error call stack বেয়ে প্রথম try/catch পর্যন্ত ওঠে; কেউ না ধরলে uncaught.
  2. সবসময় Error object ছুড়ুন; সিদ্ধান্ত নিন instanceof, name বা নিজের field দিয়ে — message বা stack-এর লেখা দিয়ে নয়।
  3. finally সবসময় চলে, এমনকি return-এর পরেও; কিন্তু finally-তে return কখনো নয় — সেটা ফল আর error দুটোই মুছে দেয়।
  4. যা সামলাতে পারেন শুধু তা ধরুন, বাকিটা rethrow; মোড়ানোর সময় { cause: err }; প্রত্যাশিত ব্যর্থতায় result object.
  5. Callback আর timer-এর error তাদের ভেতরে ধরুন; global handler শুধু রিপোর্টের জন্য; ব্যবহারকারীকে বোধগম্য বার্তা, stack trace আর গোপন তথ্য কখনো নয়।

পরের পর্বে: এই পর্বে ফোন নম্বর যাচাই করেছি শুধু দৈর্ঘ্য দেখে — কিন্তু “০১ দিয়ে শুরু, তারপর ৩–৯, মোট ১১ অঙ্ক” কীভাবে এক লাইনে যাচাই করবেন? Regular expression — লেখায় নকশা খোঁজা, যাচাই আর বদলানো।

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

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