Error Handling & Custom Errors

গ্রাহকের সামনে চুপ থাকা নয় — সঠিক ভাবে fail করুন

~30 min Intermediate 12 practice problems Live runner

1. try / catch / finally

tcf.js
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");
প্রতিটি throw একটি Error subclass হওয়া উচিত — string throw না করে। তাহলে stack trace, name এবং message সব পাওয়া যাবে।

3. Custom Error Classes

custom.js
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.

cause.js
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 async function — use try/catch around await
  • Outside — chain a final .catch on 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 throw over silent return null for 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 (শব্দকোষ)

TermMeaningবাংলায়
throwStatement that raises an exception (always throw an Error).Exception raise করার statement।
try/catchBlock that traps exceptions thrown inside it.ভেতরে throw হওয়া exception ধরে।
finallyCleanup block — runs whether or not an error occurred.Error হোক বা না হোক — সবসময় চলে।
ErrorBuilt-in class with name, message, stack.Built-in class — name/message/stack থাকে।
TypeErrorWrong type used (e.g. calling a non-function).Type ভুল — যেমন non-function call।
RangeErrorNumber out of valid range.সংখ্যা valid range-এর বাইরে।
Custom ErrorClass that extends Error with a meaningful name.Error-এর subclass; meaningful name।
causeES2022 — wrap a low-level error inside a higher one.ES2022 — মূল error সংরক্ষণ করে।
RethrowCatch an error you can't handle and throw it onward.যা handle করতে পারছেন না — সেটি আবার throw।
unhandledrejectionEvent for promise rejections that nothing caught.কোনো catch না-পাওয়া promise rejection-এর event।
মনে রাখবেন: Silent failure সবচেয়ে খারাপ kind-এর bug — কখনোই catch (e) {} খালি রাখবেন না। সবসময় Error subclass throw করুন; instanceof দিয়ে চিহ্নিত করুন; না বুঝলে rethrow করুন। ES2022-এর cause দিয়ে original error সংরক্ষণ — debug-এ অমূল্য।

8. Practice Problems

  1. Catch a JSON.parse error and return an empty object.
    ✨ Show Answer
    a1.js
    const safe = s => { try { return JSON.parse(s); } catch { return {}; } };
    console.log(safe("x"));
    console.log(safe('{"a":1}'));
  2. Throw a TypeError when a non-number is passed to add(a, b).
    ✨ Show Answer
    a2.js
    function 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); }
  3. Define a custom NotFoundError class.
    ✨ Show Answer
    a3.js
    class 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); }
  4. Wrap a low-level error using ES2022 cause.
    ✨ Show Answer
    a4.js
    try {
        try { JSON.parse("x"); }
        catch (e) { throw new Error("settings broken", { cause: e }); }
    } catch (e) {
        console.log(e.message, "→", e.cause.message);
    }
  5. 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);
        }
    })();
  6. Why is "throw 'message'" worse than "throw new Error(...)"?
    ✨ Show Answer

    Answer: A bare string has no .stack, no .name, and no instanceof Error. Any code that distinguishes errors by class fails. Always throw an Error (or subclass) — the engine fills in the stack trace automatically.

  7. Build a runnable retry that swallows specific errors and rethrows others.
    ✨ Show Answer
    a7.js
    class 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);
  8. Show that finally runs even when catch returns.
    ✨ Show Answer
    a8.js
    function demo() {
        try { throw new Error("x"); }
        catch { return "caught"; }
        finally { console.log("finally ran"); }
    }
    console.log(demo());
  9. 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.

  10. Listen for unhandled promise rejections globally (sketch).
    ✨ Show Answer
    addEventListener("unhandledrejection", e => {
        console.error("oops:", e.reason);
    });
  11. 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.

  12. 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 cause retains 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.

কখনো silent failure নয়। Custom Error class দিয়ে ভিন্ন ভিন্ন ব্যর্থতা চিহ্নিত করুন। ES2022 cause দিয়ে original error সংরক্ষণ করুন।

Next Module → Regular Expressions।