Error Handling & Custom Errors
গ্রাহকের সামনে চুপ থাকা নয় — সঠিক ভাবে fail করুন
1. try / catch / finally
function parse(s) {
try {
return JSON.parse(s);
} catch (e) {
console.log("bad json:", e.message);
return null;
} finally {
console.log("parse attempted");
}
}
parse('{"a":1}');
parse("not-json");
2. throw & Built-in Errors
throw new Error("generic message");
throw new TypeError("expected number");
throw new RangeError("must be 1..100");
throw new ReferenceError("undefined identifier");
throw new SyntaxError("bad source");
3. Custom Error Classes
class ValidationError extends Error {
constructor(field, msg) {
super(msg);
this.name = "ValidationError";
this.field = field;
}
}
class NetworkError extends Error {
constructor(status, msg) {
super(msg);
this.name = "NetworkError";
this.status = status;
}
}
function save(name) {
if (!name) throw new ValidationError("name", "required");
if (name.length > 50) throw new ValidationError("name", "too long");
}
try {
save("");
} catch (e) {
if (e instanceof ValidationError) {
console.log("validation failed on", e.field, "-", e.message);
} else {
throw e; // rethrow unknown errors
}
}
4. ES2022 cause
Wrap a low-level error in a higher-level one without losing the original.
function readSettings(s) {
try {
return JSON.parse(s);
} catch (e) {
throw new Error("settings file is corrupt", { cause: e });
}
}
try {
readSettings("not-json");
} catch (e) {
console.log("top:", e.message);
console.log("caused by:", e.cause.message);
}
5. Async Errors
- Inside an
asyncfunction — usetry/catcharoundawait - Outside — chain a final
.catchon the returned Promise - Set up a global handler for promises with no
.catch:addEventListener("unhandledrejection", e => { e.preventDefault(); log("unhandled:", e.reason); });
6. Defensive Programming Guidelines
✅ Do
- Throw at the boundary, not deep inside helpers
- Use Error subclasses that say what failed
- Log enough context to debug after the fact
- Prefer
throwover silentreturn nullfor unexpected states
⚠️ Avoid
- Bare
catch (e) {}— silent failure - Catching everything and rethrowing the same thing
- Using
throw "string"— loses stack trace - Hiding errors behind generic
"something went wrong"messages
7. Glossary (শব্দকোষ)
| Term | Meaning | বাংলায় |
|---|---|---|
throw | Statement that raises an exception (always throw an Error). | Exception raise করার statement। |
try/catch | Block that traps exceptions thrown inside it. | ভেতরে throw হওয়া exception ধরে। |
finally | Cleanup block — runs whether or not an error occurred. | Error হোক বা না হোক — সবসময় চলে। |
Error | Built-in class with name, message, stack. | Built-in class — name/message/stack থাকে। |
| TypeError | Wrong type used (e.g. calling a non-function). | Type ভুল — যেমন non-function call। |
| RangeError | Number out of valid range. | সংখ্যা valid range-এর বাইরে। |
| Custom Error | Class that extends Error with a meaningful name. | Error-এর subclass; meaningful name। |
cause | ES2022 — wrap a low-level error inside a higher one. | ES2022 — মূল error সংরক্ষণ করে। |
| Rethrow | Catch an error you can't handle and throw it onward. | যা handle করতে পারছেন না — সেটি আবার throw। |
unhandledrejection | Event for promise rejections that nothing caught. | কোনো catch না-পাওয়া promise rejection-এর event। |
catch (e) {} খালি রাখবেন না। সবসময় Error subclass throw করুন; instanceof দিয়ে চিহ্নিত করুন; না বুঝলে rethrow করুন। ES2022-এর cause দিয়ে original error সংরক্ষণ — debug-এ অমূল্য।
8. Practice Problems
- Catch a JSON.parse error and return an empty object.
✨ Show Answer
a1.jsconst safe = s => { try { return JSON.parse(s); } catch { return {}; } }; console.log(safe("x")); console.log(safe('{"a":1}')); - Throw a TypeError when a non-number is passed to add(a, b).
✨ Show Answer
a2.jsfunction add(a, b) { if (typeof a !== "number" || typeof b !== "number") throw new TypeError("numbers only"); return a + b; } try { add(1, "x"); } catch (e) { console.log(e.name, e.message); } - Define a custom NotFoundError class.
✨ Show Answer
a3.jsclass NotFoundError extends Error { constructor(what) { super(`${what} not found`); this.name = "NotFoundError"; } } try { throw new NotFoundError("user 42"); } catch (e) { console.log(e.name, ":", e.message); } - Wrap a low-level error using ES2022 cause.
✨ Show Answer
a4.jstry { try { JSON.parse("x"); } catch (e) { throw new Error("settings broken", { cause: e }); } } catch (e) { console.log(e.message, "→", e.cause.message); } - Use try/catch around an await.
✨ Show Answer
a5.js(async () => { try { await Promise.reject(new Error("net")); } catch (e) { console.log("caught:", e.message); } })(); - Why is "throw 'message'" worse than "throw new Error(...)"?
✨ Show Answer
Answer: A bare string has no
.stack, no.name, and noinstanceof Error. Any code that distinguishes errors by class fails. Always throw anError(or subclass) — the engine fills in the stack trace automatically. - Build a runnable retry that swallows specific errors and rethrows others.
✨ Show Answer
a7.jsclass Transient extends Error {} async function retry(fn, n) { let last; for (let i = 0; i < n; i++) { try { return await fn(); } catch (e) { if (!(e instanceof Transient)) throw e; last = e; } } throw last; } let i = 0; retry(async () => { if (++i < 3) throw new Transient(); return "ok"; }, 5).then(console.log); - Show that finally runs even when catch returns.
✨ Show Answer
a8.jsfunction demo() { try { throw new Error("x"); } catch { return "caught"; } finally { console.log("finally ran"); } } console.log(demo()); - List 3 reasons silent failure is the worst kind of bug.
✨ Show Answer
(1) The user sees stale data without realising it. (2) Logs are clean — you never know it failed until support tickets pile up. (3) Adjacent code keeps running on bad state, multiplying the damage.
- Listen for unhandled promise rejections globally (sketch).
✨ Show Answer
addEventListener("unhandledrejection", e => { console.error("oops:", e.reason); }); - Why might you rethrow an error you caught?
✨ Show Answer
Answer: To handle only the cases you understand and let unknown ones bubble up to a higher layer (or the global handler). Rethrowing preserves the stack trace, unlike returning
null. - In one paragraph, when should you use
cause?✨ Show Answer
Answer: When you want to translate a low-level failure into a domain-meaningful one without losing the original. The outer error gives the caller a stable contract ("settings broken"), while the chained
causeretains the technical detail (the JSON parse error) for debugging and logs.
Summary — Module 31
Throw Error subclasses, catch them by instanceof, rethrow what you don't understand. Use cause to preserve original errors when wrapping. Around await, plain try/catch works. Silent catch (e) {} is the worst pattern in production JS.
cause দিয়ে original error সংরক্ষণ করুন।