Callbacks & Callback Hell
পুরোনো async — যা Promise-এর জন্ম দিয়েছিল
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).
// 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);
});
err। ভুলে চেক না করলে failure silently পেরিয়ে যায় — এই সমস্যা Promise-এর অন্যতম কারণ।3. The Pyramid of Doom
Three sequential async steps and your code starts looking like a Christmas tree.
// 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
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
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 (শব্দকোষ)
| Term | Meaning | বাংলায় |
|---|---|---|
| Callback | A function passed to another function to be invoked later. | আরেকটি function-এ পাঠানো function — পরে call হবে। |
| Sync callback | Invoked immediately within the call (e.g. arr.map). | Call-এর ভেতরেই তৎক্ষণাৎ চলে। |
| Async callback | Invoked later by the runtime (e.g. setTimeout). | Runtime পরে call করে। |
| Error-first | Node convention: cb(err, data); err is null on success. | Node-এর convention: প্রথম arg err; success হলে null। |
| Pyramid of doom | Deeply nested callbacks that drift to the right. | Nested callback-এর কারণে ডানে গড়িয়ে যাওয়া কোড। |
| Inversion of control | Trusting another function to call yours correctly — risky. | আপনার callback অন্যের হাতে — দায়িত্ব হারানো। |
setTimeout | Schedules a single call after N ms. | N ms পরে এক বার call করে। |
setInterval | Schedules repeated calls every N ms. | প্রতি N ms পর পর call করে। |
| Promisify | Wrap a callback API in a Promise. | Callback API-কে Promise-এ মোড়ানো। |
err চেক করতে হয় এবং নিজের control অন্যের হাতে চলে যায়। Promise এসব সমস্যার পরিচ্ছন্ন সমাধান — পরের module-এ।
8. Practice Problems
- Write a delay function
delay(ms, cb).✨ Show Answer
a1.jsconst delay = (ms, cb) => setTimeout(cb, ms); delay(50, () => console.log("hi")); - Print 1, 2, 3 in order using nested setTimeouts.
✨ Show Answer
a2.jssetTimeout(() => { console.log(1); setTimeout(() => { console.log(2); setTimeout(() => console.log(3), 10); }, 10); }, 10); - Build an error-first
safeDivide(a, b, cb)that errors on b=0.✨ Show Answer
a3.jsconst 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)); - Use setInterval to print "tick" three times then stop.
✨ Show Answer
a4.jslet n = 0; const id = setInterval(() => { console.log("tick", ++n); if (n === 3) clearInterval(id); }, 30); - Refactor a 3-deep callback pyramid into a promise chain.
✨ Show Answer
a5.jsconst 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); - Promisify a callback function: write
promisify(fn).✨ Show Answer
a6.jsconst 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); - 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.
- Run two independent timers and prove they don't block each other.
✨ Show Answer
a8.jsconst t0 = Date.now(); setTimeout(() => console.log("a", Date.now() - t0), 50); setTimeout(() => console.log("b", Date.now() - t0), 30); - Build a
once(fn)wrapper using a callback-style API.✨ Show Answer
a9.jsconst 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"); - 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.