JavaScript’s asynchronous behavior is at the heart of modern web applications, especially when dealing with API calls, timers, or user interactions. Over time, three major patterns have evolved to handle asynchronous workflows: Callbacks, Promises, and Async/Await. While all three achieve the same goal, their syntax and developer experience vary significantly. This simplified comparison uses one consistent example to clearly show how each method works and why newer approaches offer cleaner and more maintainable code. Lets understand it with below example:
Ordering Food Online (Same steps for all 3 examples)
1️⃣ Order accepted
2️⃣ Food prepared
3️⃣ Food delivered
We will use one set of API-like functions for all examples.
🍽️ Shared Functions (Used Everywhere)
These simulate dummy API calls using setTimeout.
function orderFoodAPI() {
return new Promise((resolve) => {
setTimeout(() => resolve("Restaurant accepted order"), 1000);
});
}
function prepareFoodAPI() {
return new Promise((resolve) => {
setTimeout(() => resolve("Food prepared"), 1500);
});
}
function deliverFoodAPI() {
return new Promise((resolve) => {
setTimeout(() => resolve("Food delivered"), 2000);
});
}✔ Now we won’t repeat these again.
1️⃣ CALLBACK VERSION
⭐ Concept: Pass a function that runs after task finishes
function orderFood(callback) {
orderFoodAPI().then(result => {
console.log(result);
callback();
});
}
function prepareFood(callback) {
prepareFoodAPI().then(result => {
console.log(result);
callback();
});
}
function deliverFood(callback) {
deliverFoodAPI().then(result => {
console.log(result);
callback();
});
}
orderFood(() => {
prepareFood(() => {
deliverFood(() => {
console.log("Enjoy your meal! 🍕");
});
});
});❌ Callback Hell
Nested, messy, hard to maintain.
2️⃣ PROMISE VERSION
⭐ Concept: “When this is done, then do this”
orderFoodAPI()
.then(result => {
console.log(result);
return prepareFoodAPI();
})
.then(result => {
console.log(result);
return deliverFoodAPI();
})
.then(result => {
console.log(result);
console.log("Enjoy your meal! 🍕");
})
.catch(() => console.log("Order failed ❌"));✔ Cleaner than callbacks
But .then().then().then() grows long.
3️⃣ ASYNC / AWAIT VERSION
⭐ Concept: Write async code like synchronous steps
async function startOrder() {
try {
console.log(await orderFoodAPI());
console.log(await prepareFoodAPI());
console.log(await deliverFoodAPI());
console.log("Enjoy your meal! 🍕");
} catch {
console.log("Order failed ❌");
}
}
startOrder();⭐ Cleanest because
Looks like simple step-by-step instructions.
<div class="infographic-asyncshapes">
<style>
.infographic-asyncshapes {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
--bad: #c8452e;
--bad-soft: #f7e3df;
box-sizing: border-box;
max-width: 900px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-asyncshapes[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--good-soft: #16281f;
--bad: #e0715a;
--bad-soft: #2e211d;
}
.infographic-asyncshapes * { box-sizing: border-box; }
.infographic-asyncshapes h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-asyncshapes p { margin: 0; }
.as-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.as-hero { text-align: center; margin-bottom: 1.5rem; }
.as-hero h1 { font-size: clamp(1.4rem, 4.5vw, 1.95rem); }
.as-hero p { color: var(--ink-soft); margin-top: 0.45rem; font-size: 0.86rem; }
.as-cols { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0.7rem; margin-bottom: 1.6rem; }
@media (max-width: 760px) { .as-cols { grid-template-columns: 1fr; } }
.as-window { border-radius: 10px; overflow: hidden; box-shadow: 0 6px 20px rgba(0,0,0,0.3); display: flex; flex-direction: column; }
.as-titlebar { background: #2b2b2b; padding: 0.5rem 0.7rem; display: flex; align-items: center; gap: 0.4rem; }
.as-dot { width: 9px; height: 9px; border-radius: 50%; }
.as-dot.red { background: #ff5f57; }
.as-dot.yellow { background: #febc2e; }
.as-dot.green { background: #28c840; }
.as-filename { flex: 1; text-align: center; color: #9a9a9a; font-family: 'IBM Plex Mono', monospace; font-size: 0.62rem; margin-right: 2.4rem; }
.as-code { background: #1e1e1e; color: #d4d4d4; padding: 0.7rem 0.6rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.56rem; line-height: 1.7; overflow-x: auto; min-height: 280px; }
.as-code .ln { display: block; white-space: pre; padding: 0 0.25rem; border-radius: 3px; transition: background 0.3s ease; }
.as-code .ln.is-active { background: rgba(37,99,235,0.28); }
.as-window.is-done .as-titlebar { background: #1c3a2c; transition: background 0.4s ease; }
.as-runrow { display: flex; align-items: center; justify-content: center; gap: 1rem; margin: -0.6rem 0 1.4rem; flex-wrap: wrap; }
.as-play { border: none; background: var(--accent); color: #fff; padding: 0.5rem 1.2rem; font-size: 0.8rem; font-weight: 600; cursor: pointer; }
.as-play:active { transform: scale(0.96); }
.as-play:disabled { opacity: 0.45; cursor: default; }
.as-elapsed { font-family: 'IBM Plex Mono', monospace; font-size: 0.75rem; color: var(--ink-soft); }
.as-elapsed strong { color: var(--ink); }
.tok-kw { color: #569cd6; }
.tok-fn { color: #dcdcaa; }
.tok-str { color: #ce9178; }
.tok-var { color: #9cdcfe; }
.tok-punc { color: #d4d4d4; }
/* output terminal */
.as-terminal { background: #0c0c0c; color: #e5e5e5; padding: 0.6rem 0.7rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.62rem; line-height: 1.8; }
.as-terminal .prompt { color: #6ee7b7; }
.as-terminal .path { color: #7dd3fc; }
.as-terminal .out { display: block; opacity: 0; transition: opacity 0.3s ease; color: #f4bf75; }
.as-terminal .out.is-shown { opacity: 1; }
.as-terminal .cursor { display: inline-block; width: 6px; height: 1em; background: #e5e5e5; vertical-align: text-bottom; margin-left: 2px; animation: as-blink 1s steps(1) infinite; }
@keyframes as-blink { 50% { opacity: 0; } }
.as-collabel { text-align: center; font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem; font-weight: 700; color: var(--ink-soft); padding: 0.4rem 0; }
@media (prefers-reduced-motion: reduce) {
.infographic-asyncshapes *, .infographic-asyncshapes *::before, .infographic-asyncshapes *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="as-wrap">
<div class="as-hero">
<h1>Callback vs Promise vs Async/Await</h1>
<p>Same login → videos → details chain, three shapes.</p>
</div>
<div class="as-runrow">
<button class="as-play" id="asPlay" type="button">run all three</button>
<div class="as-elapsed">t = <strong id="asElapsed">0s</strong></div>
</div>
<div class="as-cols">
<div>
<div class="as-collabel">Callback</div>
<div class="as-window" id="winCallback">
<div class="as-titlebar"><div class="as-dot red"></div><div class="as-dot yellow"></div><div class="as-dot green"></div><div class="as-filename">callback.js</div></div>
<div class="as-code" id="codeCallback"><div class="ln" data-step="1"><span class="tok-fn">loginUser</span><span class="tok-punc">(email, pass, (user) => {</span></div><div class="ln" data-step="2"><span> </span><span class="tok-fn">getUserVideos</span><span class="tok-punc">(user, (videos) => {</span></div><div class="ln" data-step="3"><span> </span><span class="tok-fn">videoDetails</span><span class="tok-punc">(videos[</span><span class="tok-var">0</span><span class="tok-punc">], (title) => {</span></div><div class="ln" data-step="4"><span> </span><span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(title);</span></div><div class="ln" data-step="4"><span> </span><span class="tok-punc">});</span></div><div class="ln" data-step="4"><span> </span><span class="tok-punc">});</span></div><div class="ln" data-step="4"><span class="tok-punc">});</span></div></div>
<div class="as-terminal" id="termCallback"><span class="prompt">➜</span> <span class="path">~</span> node callback.js<br><span class="out" id="outCallback">"title of the video"</span><span class="prompt">➜</span> <span class="path">~</span> <span class="cursor"></span></div>
</div>
</div>
<div>
<div class="as-collabel">Promise</div>
<div class="as-window" id="winPromise">
<div class="as-titlebar"><div class="as-dot red"></div><div class="as-dot yellow"></div><div class="as-dot green"></div><div class="as-filename">promise.js</div></div>
<div class="as-code" id="codePromise"><div class="ln" data-step="1"><span class="tok-fn">loginUser</span><span class="tok-punc">(email, pass)</span></div><div class="ln" data-step="1"><span> </span><span class="tok-punc">.</span><span class="tok-fn">then</span><span class="tok-punc">((user) =></span></div><div class="ln" data-step="2"><span> </span><span class="tok-fn">getUserVideos</span><span class="tok-punc">(user))</span></div><div class="ln" data-step="2"><span> </span><span class="tok-punc">.</span><span class="tok-fn">then</span><span class="tok-punc">((videos) =></span></div><div class="ln" data-step="3"><span> </span><span class="tok-fn">videoDetails</span><span class="tok-punc">(videos[</span><span class="tok-var">0</span><span class="tok-punc">]))</span></div><div class="ln" data-step="3"><span> </span><span class="tok-punc">.</span><span class="tok-fn">then</span><span class="tok-punc">((title) =></span></div><div class="ln" data-step="4"><span> </span><span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(title))</span></div><div class="ln" data-step="4"><span> </span><span class="tok-punc">.</span><span class="tok-fn">catch</span><span class="tok-punc">((err) =></span></div><div class="ln" data-step="4"><span> </span><span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(err));</span></div></div>
<div class="as-terminal" id="termPromise"><span class="prompt">➜</span> <span class="path">~</span> node promise.js<br><span class="out" id="outPromise">"title of the video"</span><span class="prompt">➜</span> <span class="path">~</span> <span class="cursor"></span></div>
</div>
</div>
<div>
<div class="as-collabel">Async / Await</div>
<div class="as-window" id="winAsync">
<div class="as-titlebar"><div class="as-dot red"></div><div class="as-dot yellow"></div><div class="as-dot green"></div><div class="as-filename">async.js</div></div>
<div class="as-code" id="codeAsync"><div class="ln" data-step="0"><span class="tok-kw">async function</span> <span class="tok-fn">run</span><span class="tok-punc">() {</span></div><div class="ln" data-step="0"><span> </span><span class="tok-kw">try</span> <span class="tok-punc">{</span></div><div class="ln" data-step="1"><span> </span><span class="tok-kw">const</span> <span class="tok-var">user</span> <span class="tok-punc">=</span></div><div class="ln" data-step="1"><span> </span><span class="tok-kw">await</span> <span class="tok-fn">loginUser</span><span class="tok-punc">(email, pass);</span></div><div class="ln" data-step="2"><span> </span><span class="tok-kw">const</span> <span class="tok-var">videos</span> <span class="tok-punc">=</span></div><div class="ln" data-step="2"><span> </span><span class="tok-kw">await</span> <span class="tok-fn">getUserVideos</span><span class="tok-punc">(user);</span></div><div class="ln" data-step="3"><span> </span><span class="tok-kw">const</span> <span class="tok-var">title</span> <span class="tok-punc">=</span></div><div class="ln" data-step="3"><span> </span><span class="tok-kw">await</span> <span class="tok-fn">videoDetails</span><span class="tok-punc">(videos[</span><span class="tok-var">0</span><span class="tok-punc">]);</span></div><div class="ln" data-step="4"><span> </span><span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(title);</span></div><div class="ln" data-step="0"><span> </span><span class="tok-punc">} </span><span class="tok-kw">catch</span> <span class="tok-punc">(err) {</span></div><div class="ln" data-step="0"><span> </span><span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(err);</span></div><div class="ln" data-step="0"><span> </span><span class="tok-punc">}</span></div><div class="ln" data-step="0"><span class="tok-punc">}</span></div></div>
<div class="as-terminal" id="termAsync"><span class="prompt">➜</span> <span class="path">~</span> node async.js<br><span class="out" id="outAsync">"title of the video"</span><span class="prompt">➜</span> <span class="path">~</span> <span class="cursor"></span></div>
</div>
</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-asyncshapes')
|| document.querySelector('.infographic-asyncshapes');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const playBtn = wrapper.querySelector('#asPlay');
const elapsed = wrapper.querySelector('#asElapsed');
const codes = [
wrapper.querySelector('#codeCallback'),
wrapper.querySelector('#codePromise'),
wrapper.querySelector('#codeAsync'),
];
const windows = [
wrapper.querySelector('#winCallback'),
wrapper.querySelector('#winPromise'),
wrapper.querySelector('#winAsync'),
];
const outLines = [
wrapper.querySelector('#outCallback'),
wrapper.querySelector('#outPromise'),
wrapper.querySelector('#outAsync'),
];
function setStep(n) {
codes.forEach(code => {
code.querySelectorAll('.ln').forEach(l => l.classList.toggle('is-active', Number(l.dataset.step) === n));
});
}
playBtn.addEventListener('click', () => {
playBtn.disabled = true;
windows.forEach(w => w.classList.remove('is-done'));
outLines.forEach(o => o.classList.remove('is-shown'));
setStep(-1);
elapsed.textContent = '0s';
const W1 = 700, W2 = 1400, W3 = 2100; // scaled 1s / 2s / 3s
let t = 100;
setTimeout(() => { setStep(1); }, t);
t += W1;
setTimeout(() => { setStep(2); elapsed.textContent = '1s'; }, t);
t += W2;
setTimeout(() => { setStep(3); elapsed.textContent = '3s'; }, t);
t += W3;
setTimeout(() => { setStep(4); elapsed.textContent = '6s'; }, t);
t += 300;
setTimeout(() => { outLines.forEach(o => o.classList.add('is-shown')); }, t);
t += 450;
setTimeout(() => {
setStep(-1);
windows.forEach(w => w.classList.add('is-done'));
playBtn.disabled = false;
}, t);
});
})();
</script>🏆 FINAL COMPARISON TABLE
Feature | Callback | Promise | Async/Await |
|---|---|---|---|
Readability | ❌ Worst | ✔ Good | ⭐ Best |
Structure | Nested | Chained | Straight |
Error Handling | Poor | Good | Best |
Modern Usage | Low | Medium | ⭐ High |
Best For | Small tasks | Medium workflows | Real-world async code |