Callbacks & Callback Hell

পুরোনো async — যা Promise-এর জন্ম দিয়েছিল

~30 min Intermediate 10 practice problems Live runner

1. What Is a Callback?

A callback is a function passed as an argument so the caller can invoke it later — synchronously (e.g. arr.map) or asynchronously (e.g. setTimeout, fetch).

basic.js
// Sync callback
[1, 2, 3].forEach(n => console.log("sync:", n));

// Async callback
setTimeout(() => console.log("async after 100ms"), 100);
console.log("main done");

2. The Node Error-First Convention

Before Promises, every Node API followed one rule: the callback's first argument is the error (null if none); subsequent args are the data.

// Real fs API
fs.readFile("hello.txt", "utf8", (err, data) => {
    if (err) return console.error("read failed:", err);
    console.log(data);
});
Node.js-এর প্রতিটি পুরোনো API-তে callback-এর প্রথম argument err। ভুলে চেক না করলে failure silently পেরিয়ে যায় — এই সমস্যা Promise-এর অন্যতম কারণ।

3. The Pyramid of Doom

Three sequential async steps and your code starts looking like a Christmas tree.

pyramid.js
// Fake async — same shape as fs/db calls
function step(name, ms, cb) {
    setTimeout(() => cb(null, name + " done"), ms);
}

step("login", 10, (err, r1) => {
    if (err) return console.error(err);
    console.log(r1);
    step("fetch profile", 10, (err, r2) => {
        if (err) return console.error(err);
        console.log(r2);
        step("load posts", 10, (err, r3) => {
            if (err) return console.error(err);
            console.log(r3);
            step("render", 10, (err, r4) => {
                console.log(r4);
            });
        });
    });
});

Each level adds another layer of indentation, error-handling duplication, and impossibility of refactoring. This is "callback hell."

4. Inversion of Control

You hand your continuation to someone else's library and trust it. The library may:

  • Call your callback twice (double-charge)
  • Never call it (lost work)
  • Call it synchronously when you expected async (re-entrancy bug)
  • Pass garbage args
  • Throw — and you can't catch it

Promises restore control: you decide what happens with the result, and the language guarantees they settle exactly once.

5. setTimeout & setInterval

timers.js
const id = setTimeout(() => console.log("once"), 100);
// clearTimeout(id) cancels it

let n = 0;
const tick = setInterval(() => {
    n++;
    console.log("tick", n);
    if (n === 3) clearInterval(tick);
}, 50);

6. Refactor — From Pyramid to Promise Chain

flat.js
const step = (name, ms) =>
    new Promise(res => setTimeout(() => res(name + " done"), ms));

step("login", 10)
    .then(r => (console.log(r), step("profile", 10)))
    .then(r => (console.log(r), step("posts",   10)))
    .then(r => (console.log(r), step("render",  10)))
    .then(console.log)
    .catch(console.error);

Same logic, flat shape, single .catch for the whole chain. Next module: Promises in depth.

7. Glossary (শব্দকোষ)

TermMeaningবাংলায়
CallbackA function passed to another function to be invoked later.আরেকটি function-এ পাঠানো function — পরে call হবে।
Sync callbackInvoked immediately within the call (e.g. arr.map).Call-এর ভেতরেই তৎক্ষণাৎ চলে।
Async callbackInvoked later by the runtime (e.g. setTimeout).Runtime পরে call করে।
Error-firstNode convention: cb(err, data); err is null on success.Node-এর convention: প্রথম arg err; success হলে null।
Pyramid of doomDeeply nested callbacks that drift to the right.Nested callback-এর কারণে ডানে গড়িয়ে যাওয়া কোড।
Inversion of controlTrusting another function to call yours correctly — risky.আপনার callback অন্যের হাতে — দায়িত্ব হারানো।
setTimeoutSchedules a single call after N ms.N ms পরে এক বার call করে।
setIntervalSchedules repeated calls every N ms.প্রতি N ms পর পর call করে।
PromisifyWrap a callback API in a Promise.Callback API-কে Promise-এ মোড়ানো।
সংক্ষেপে: Callback async-এর পুরোনো কৌশল — ছোট কাজে ঠিক, কিন্তু পরপর কয়েক ধাপ-এ পিরামিড হয়ে যায়। প্রতি level-এ err চেক করতে হয় এবং নিজের control অন্যের হাতে চলে যায়। Promise এসব সমস্যার পরিচ্ছন্ন সমাধান — পরের module-এ।

8. Practice Problems

  1. Write a delay function delay(ms, cb).
    ✨ Show Answer
    a1.js
    const delay = (ms, cb) => setTimeout(cb, ms);
    delay(50, () => console.log("hi"));
  2. Print 1, 2, 3 in order using nested setTimeouts.
    ✨ Show Answer
    a2.js
    setTimeout(() => {
        console.log(1);
        setTimeout(() => {
            console.log(2);
            setTimeout(() => console.log(3), 10);
        }, 10);
    }, 10);
  3. Build an error-first safeDivide(a, b, cb) that errors on b=0.
    ✨ Show Answer
    a3.js
    const safeDivide = (a, b, cb) => {
        if (b === 0) return cb(new Error("divide by zero"));
        cb(null, a / b);
    };
    safeDivide(10, 2, (err, r) => console.log(err, r));
    safeDivide(10, 0, (err, r) => console.log(err.message));
  4. Use setInterval to print "tick" three times then stop.
    ✨ Show Answer
    a4.js
    let n = 0;
    const id = setInterval(() => {
        console.log("tick", ++n);
        if (n === 3) clearInterval(id);
    }, 30);
  5. Refactor a 3-deep callback pyramid into a promise chain.
    ✨ Show Answer
    a5.js
    const step = (n) => new Promise(r => setTimeout(() => r(n), 10));
    step("a")
        .then(console.log)
        .then(() => step("b")).then(console.log)
        .then(() => step("c")).then(console.log);
  6. Promisify a callback function: write promisify(fn).
    ✨ Show Answer
    a6.js
    const promisify = fn => (...args) =>
        new Promise((res, rej) =>
            fn(...args, (err, data) => err ? rej(err) : res(data)));
    
    const sumCb = (a, b, cb) => cb(null, a + b);
    const sumP  = promisify(sumCb);
    sumP(2, 3).then(console.log);
  7. List 3 problems with passing your callback to a 3rd-party library.
    ✨ Show Answer

    Answer: (1) The library may call it twice or never. (2) It may call sync when you expected async (re-entrancy hazards). (3) An error thrown inside your callback might be swallowed by the library and never logged.

  8. Run two independent timers and prove they don't block each other.
    ✨ Show Answer
    a8.js
    const t0 = Date.now();
    setTimeout(() => console.log("a", Date.now() - t0), 50);
    setTimeout(() => console.log("b", Date.now() - t0), 30);
  9. Build a once(fn) wrapper using a callback-style API.
    ✨ Show Answer
    a9.js
    const once = fn => {
        let done = false;
        return (...a) => { if (!done) { done = true; fn(...a); } };
    };
    const log = once((m) => console.log("called:", m));
    log("a"); log("b"); log("c");
  10. In one paragraph, explain why callbacks made the world move to Promises.
    ✨ Show Answer

    Answer: Callbacks scale poorly when you chain async steps — error handling has to be repeated at every level, the code grows rightward into a pyramid, and you've handed your control flow to whoever called the callback. Promises invert that: the producer creates a Promise object, you keep the chain on your side, error handling collapses to one .catch, and the language guarantees the result fires exactly once.

Summary — Module 21

Callbacks are the original async style. They work but don't scale: nesting becomes a pyramid, error handling repeats, and you trust the caller to obey the contract. Promises (next module) rewrite the rules.

Callback পুরোনো async — ছোট কোডে ঠিক, বড় chain-এ ভয়ংকর। Promise এই সমস্যার সমাধান।

Next Module → Promises & Combinators।