Day 19: Async JS: Event Loop, Callbacks & Promises
✅ Today you will learn
- Why JavaScript needs async code
- The event loop: call stack, task queue, microtasks
- Timers: setTimeout, setInterval, clearTimeout
- Callbacks and why callback hell happens
- Promises: create, then/catch/finally, Promise.all / allSettled / race / any
🏢 In the real world
Loading the menu from a server takes 300 ms on Wi-Fi and 3 seconds on a weak 4G signal. If JavaScript waited and did nothing else, the page would freeze – no scrolling, no clicking. Async code lets the browser keep working while it waits for the network, a timer or a payment gateway. Every API call you make tomorrow builds on today’s ideas.
Synchronous vs asynchronous
console.log('1. Take the order');
setTimeout(() => console.log('3. Sweets are ready (after 0 ms!)'), 0);
console.log('2. Take the next customer’s order');JavaScript runs one thing at a time (single thread). setTimeout hands the callback to the browser/Node and moves on. The callback waits in a queue until the current code finishes – only then does the event loop run it.
The event loop
console.log('A: script start');
setTimeout(() => console.log('E: setTimeout (task)'), 0);
Promise.resolve().then(() => console.log('C: promise.then (microtask)'));
queueMicrotask(() => console.log('D: queueMicrotask'));
console.log('B: script end');Timers
let seconds = 3;
const id = setInterval(() => {
console.log(`OTP expires in ${seconds}s`);
seconds--;
if (seconds === 0) {
clearInterval(id); // always stop intervals
console.log('OTP expired – request a new one');
}
}, 100); // 100 ms here so the demo is quick
const reminder = setTimeout(() => console.log('This never prints'), 500);
clearTimeout(reminder); // cancelled before it firedCallbacks and callback hell
// Pretend server functions that take time and call us back
function getUser(id, cb) { setTimeout(() => cb(null, { id, name: 'Aarti' }), 50); }
function getOrders(user, cb) { setTimeout(() => cb(null, [{ id: 1042, total: 1800 }]), 50); }
function getInvoice(order, cb) { setTimeout(() => cb(null, `Invoice for #${order.id}: ₹${order.total}`), 50); }
getUser(7, (err, user) => {
if (err) return console.log(err);
getOrders(user, (err, orders) => {
if (err) return console.log(err);
getInvoice(orders[0], (err, invoice) => { // the "pyramid of doom" grows →
if (err) return console.log(err);
console.log(user.name, '→', invoice);
});
});
});Each step nests deeper, error handling is repeated at every level and running steps in parallel is hard. Promises (today) and async/await (tomorrow) solve exactly this.
Promises
// A promise = an object that will hold a future result: pending → fulfilled OR rejected
function bakeGhewar(kg) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (kg > 20) reject(new Error('We can make at most 20 kg today'));
else resolve(`${kg} kg ghewar ready`);
}, 100);
});
}
bakeGhewar(5)
.then(result => { console.log('✅', result); return bakeGhewar(50); }) // return → chain the next step
.then(result => console.log('never printed', result))
.catch(err => console.log('❌', err.message)) // one catch for the whole chain
.finally(() => console.log('Kitchen closed for today'));| State | Meaning | Handled by |
|---|---|---|
| pending | Still working | — |
| fulfilled | Finished successfully with a value | .then(value => …) |
| rejected | Failed with an error | .catch(err => …) |
| settled (either) | Done, success or failure | .finally(() => …) |
The callback pyramid, rewritten with promises
const wait = (ms, value) => new Promise(resolve => setTimeout(() => resolve(value), ms));
const getUser = id => wait(50, { id, name: 'Aarti' });
const getOrders = user => wait(50, [{ id: 1042, total: 1800 }]);
const getInvoice = order => wait(50, `Invoice for #${order.id}: ₹${order.total}`);
let currentUser;
getUser(7)
.then(user => { currentUser = user; return getOrders(user); })
.then(orders => getInvoice(orders[0]))
.then(invoice => console.log(currentUser.name, '→', invoice))
.catch(err => console.log('Something failed:', err.message)); // flat, one error handlerRunning promises together
const wait = (ms, v, fail) => new Promise((res, rej) => setTimeout(() => (fail ? rej(new Error(v)) : res(v)), ms));
const menu = wait(120, 'menu');
const offers = wait(80, 'offers');
const reviews = wait(100, 'reviews');
console.time('all');
Promise.all([menu, offers, reviews]).then(r => { console.log('all:', r); console.timeEnd('all'); }); // ~120 ms, not 300
Promise.allSettled([wait(50, 'menu'), wait(60, 'reviews API down', true)])
.then(results => console.log('allSettled:', results.map(r => r.status + ':' + (r.value ?? r.reason.message)).join(', ')));
Promise.race([wait(200, 'slow server'), wait(90, 'timeout – show cached menu')])
.then(winner => console.log('race:', winner));
Promise.any([wait(70, 'mirror 1 down', true), wait(110, 'mirror 2 OK')])
.then(first => console.log('any:', first));| Method | Resolves when | Rejects when | Use for |
|---|---|---|---|
| Promise.all | All succeed (array of results) | Any one fails | Page needs menu + offers + cart together |
| Promise.allSettled | All finish (status of each) | Never | Dashboard widgets that may fail independently |
| Promise.race | The first settles | The first settles with an error | Timeouts |
| Promise.any | The first succeeds | All fail | Fastest of several mirrors/CDNs |
Real task: a “preparing your order” tracker
<button id="go">Place order</button>
<ol id="steps"></ol>
<script>
const wait = ms => new Promise(r => setTimeout(r, ms));
const steps = document.querySelector('#steps');
function show(text) { const li = document.createElement('li'); li.textContent = text; steps.append(li); }
document.querySelector('#go').addEventListener('click', e => {
e.target.disabled = true;
steps.innerHTML = '';
show('🧾 Order received');
wait(800)
.then(() => { show('👩🍳 Sweets being packed'); return wait(1000); })
.then(() => { show('🛵 Out for delivery'); return wait(1200); })
.then(() => show('✅ Delivered – enjoy!'))
.finally(() => { e.target.disabled = false; });
});
</script>🏅 Job skills unlocked today
Practice tasks
🏋️ Practice – do it yourself
Show solution
const wait = (ms, v) => new Promise(r => setTimeout(() => r(v), ms));
function timeout(promise, ms) {
const timer = new Promise((_, reject) => setTimeout(() => reject(new Error('Request timed out')), ms));
return Promise.race([promise, timer]);
}
timeout(wait(50, 'menu loaded'), 200).then(console.log).catch(e => console.log(e.message));
timeout(wait(500, 'too slow'), 200).then(console.log).catch(e => console.log(e.message));Show solution
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
(async () => { console.log(4); await null; console.log(5); })();
console.log(6);
// expected: 1 4 6 3 5 2Quick quiz
🧠 Quick Quiz – test yourself
Interview corner
💼 Interview Corner
Explain the event loop.
JavaScript executes on a single call stack. Async operations are handled by the host (browser/Node); their callbacks go to queues. When the stack is empty the event loop first drains the microtask queue (promise reactions, queueMicrotask), then takes the next task (timers, I/O, events), and renders between tasks in browsers.
Promise.all vs Promise.allSettled?
all resolves with all values or rejects on the first failure (fail-fast) – use when every result is required. allSettled waits for all and reports each status/value/reason – use when partial results are useful.
Summary
- One thread + event loop; async work waits in queues.
- Microtasks (promises) run before tasks (timers, events).
- Promises: pending → fulfilled/rejected; then/catch/finally; return to chain.
- all / allSettled / race / any for parallel work and timeouts.
Day 20: async/await and fetch – load real products from an API with loading and error states.
