▸ About the series
A three-part progression from "what gets logged in what order" to a full async / await migration playbook. The series was written to fix the three most common misconceptions about JavaScript's runtime:
- "
setTimeout(fn, 0)runs immediately." (No — it sits behind every pending microtask and any earlier-queued timer.) - "
new Promise(...)is async." (The executor body is synchronous; only.thencallbacks are deferred.) - "A
.thenchain is one deferred unit." (It's N independent microtask queueings — two parallel chains interleave.)
Design principle
Every part pairs a runnable JS source file (src/js-q.js, src/js-q-2.js) with an explainer that traces the output character by character. Every trace was verified by running the code with Node 24 and matching against the predicted output. No hand-waving, no "trust me."
Mechanics
All three pages are fully responsive (1 / 2 / 3 columns by viewport), with a sticky table-of-contents bar on desktop and a hamburger drawer on mobile. They share styles.css + app.js (sibling files in this folder). Each part has forward / backward navigation in its header and footer so you can flip between them.
▸ The progression at a glance
await fits, 9 migration traps, a 10-point checklist, plus Parts 1 & 2 rewritten in async/await.
Part 1 Event-loop explainer #1
Step-by-step trace of an eight-statement program (src/js-q.js) that mixes console.log, setTimeout, and Promise.then — the classic interview gotcha. Builds the mental model of call stack → microtask queue → macrotask queue from scratch.
- Phase-by-phase trace with queue-state visualisation cards (stack / micro / macro / output)
- Why microtasks always beat macrotasks, even when queued later
- Why microtasks drain greedily — including microtasks queued mid-drain
- Full 11-row trace table mapping every state transition from start to exit
- Node-specific notes:
process.nextTick,setImmediate, event-loop phases
Part 2 Event-loop explainer #2
Two more counter-intuitive puzzles (src/js-q-2.js) targeting the two most common wrong mental models: "Promise is async magic" and "a .then chain runs atomically."
- The Promise executor body is synchronous — including code after
resolve() - Two parallel
.thenchains interleave one step at a time, not chain-then-chain - Side-by-side "wrong guess" vs "actual output" comparisons
- Connection to
async/await: same interleave pattern under the hood - Brief tour of V8's "Faster async functions" optimisation (1 tick instead of 2 since V8 7.2)
Part 3 await explainer & migration playbook
Where await fits in the event-loop picture, what it compiles down to, and 9 migration traps to watch for when moving a codebase from explicit .then chains to async/await.
- Mental model: side-by-side "you write" vs "the engine effectively runs"
- The 9 traps — lost parallelism,
forEach+async, fire-and-forget rejections,return await, first-await shape change, argument eagerness, await on non-thenable, microtask flooding, top-level await - What you gain — readability, unified error handling, stack traces, debugger UX
- A 10-item migration playbook checklist
- Bonus: Parts 1 and 2 rewritten in
async/awaitwith verified output and a teaching note on what shifts when you "translate" — composition can change ordering even when individual units don't
▸ How it's built
node-ts-test/ ├── cheatsheets/ │ ├── event-loop-index.html ← you are here │ ├── js-q-explainer.html ← Part 1 │ ├── js-q-2-explainer.html ← Part 2 │ ├── await-explainer.html ← Part 3 │ ├── styles.css ← shared Rose Pine, responsive grid, burger menu │ └── app.js ← shared drawer toggle └── src/ ├── js-q.js ← Part 1 puzzle source — run with `node src/js-q.js` └── js-q-2.js ← Part 2 puzzle source — run with `node src/js-q-2.js`
- Each explainer is a single HTML file linking
styles.cssandapp.js(same directory) - Only JS-specific syntax-token colours and per-page styles (trace tables, queue-state cards, phase pills) stay inline
- Every trace was verified by running the corresponding
.jsfile with Node 24 and diffing the actual output against the predicted output character-for-character - No framework, no bundler, no dependencies — three pages totalling ~155 KB
▸ Further reading — event loop, promises & async
Canonical specs & runtime docs
- HTML Spec — Event loops whatwg.org The canonical definition of "event loop" in browsers — task queues, microtask queues, the run-to-completion model.
-
Node.js — The event loop, timers, and process.nextTick
nodejs.org
How Node's libuv-driven loop differs from a browser's — phases,
setImmediatevssetTimeout(0),process.nextTickpriority. -
Node.js — Unhandled rejection modes
nodejs.org
Reference for
--unhandled-rejections=throw|warn|none|strict— what Node does when a promise rejection escapes. -
ECMA-262 — Promise Resolve Functions
tc39.es
Spec text for what
resolve()actually does. Dense but authoritative. -
TC39 — Top-level await proposal
github.com
The motivation and design of top-level
awaitin ESM modules — including the dependency-graph semantics.
MDN references
- MDN — The event loop mdn High-level summary of the runtime model; good starter reading before diving into the WHATWG spec.
-
MDN — Promise
mdn
Full API reference: constructor, static methods (
all,allSettled,race,any), instance methods. -
MDN — async function
mdn
Reference for the
asynckeyword and how it shapes function return types. -
MDN — await operator
mdn
Reference for
awaitsemantics, including the spec-equivalentPromise.resolve(x).then(...)behaviour.
Deep dives by humans
- Jake Archibald — Tasks, microtasks, queues and schedules jakearchibald.com The definitive deep-dive article. Still the clearest written explanation of how microtask queueing works after a decade.
- Jake Archibald — In the loop (JSConf.Asia 2018) youtube 45-minute talk version of the same material — animated, fast-paced, and worth the watch even if you've read the article.
- Jake Archibald — await vs return vs return await jakearchibald.com Three subtly different ways to "return a promise" from an async function — and when each one matters. Background for Part 3's trap #4.
-
V8 blog — Faster async functions and promises
v8.dev
How V8 7.2 (Chrome 73 / Node 11) eliminated the historical 2-microtask cost of
awaiton a resolved promise.
▸ Visualisers & talks
- JS Visualizer 9000 jsv9000.app Step through any JS snippet and watch the call stack, microtask queue, and macrotask queue update in real time. Pair with Part 1 and Part 2 source files for an instant a-ha moment.
- Loupe (latentflip) latentflip.com Philip Roberts' classic visualiser — the one used in his "What the heck is the event loop anyway?" talk. Slightly older (predates microtasks UI), but the call-stack animation is excellent.
- Philip Roberts — What the heck is the event loop anyway? (JSConf EU 2014) youtube The talk that introduced thousands of devs to the call-stack-plus-queue model. Older than the microtask era but still the best 25-minute intro.
▸ Credits
- Theme: Rose Pine — palette by Emilia Dunfelt & contributors. Used in its "Main" variant with the standard role mapping (love, gold, rose, pine, foam, iris).
- Fonts: system UI sans for prose; JetBrains Mono / Fira Code / SF Mono / Menlo for code blocks.
- Validation: every page checked with Python's
html.parser; every event-loop trace verified by running the corresponding.jsfile with Node 24 and comparing output character-for-character.