1 When a hello isn’t hello
JavaScript feels simple to start with, but the chapter shows how much complexity is hidden beneath that ease. It distinguishes the ECMAScript language from the engine that executes it and the host runtime that embeds the engine, emphasizing that many behaviors developers observe—especially around scheduling and asynchrony—are host-defined rather than language-defined. Concepts like the event loop, multiple task queues, and the differences between microtasks and other queues are introduced to explain why the same code can run differently across environments.
The chapter’s signature “hello” puzzle demonstrates this divergence in practice: the same short program prints different outputs in Node.js CommonJS versus ECMAScript modules, and again in Deno and Bun. The reason isn’t bugs but differing event loop designs and queue-draining policies—Node.js processes nextTicks before microtasks, browsers don’t have nextTicks, and some runtimes lack certain Node-specific APIs unless explicitly imported. The broader lesson is to avoid relying on precise timing or ordering across queues, because queue semantics (microtask, nextTick, immediates, timers) and even module mode can shift execution order. Along the way, the chapter also previews language-level surprises like floating-point quirks and coercion rules, contrasting language-defined behavior (promises) with host-defined features (process.nextTick).
Framed as an owner’s manual, the book aims to build a deep, internal understanding of how JavaScript actually runs: how engines parse, compile, and optimize code; how host runtimes schedule work and expose resources; and how those choices affect correctness and performance. Using Node.js 24 as a baseline and comparing with other runtimes, it connects real-world production issues to their underlying mechanics—covering types, promises, streams, cryptography, and modules—so readers can diagnose problems, reason about cross-environment differences, and write code that behaves predictably wherever it runs.
An illustration of the phases of the Node.js Event Loop from the project documentation (https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick). At the start of each iteration Node.js will first execute timers, then move on to calling pending callbacks, preparing for I/O, polling the operating system for I/O, then performing various checks and cleanup operations before starting over again at the top with timers.
Summary
- Identical JavaScript code can yield different results in Node.js, Deno, and Bun due to differences in runtime implementation choices
- The event loop controls execution order, with each runtime implementing different scheduling priorities
- Language specifications define what must be consistent; implementations and hosts define what can vary
- Understanding the distinction between language, implementation, and host behaviors prevents unexpected bugs
FAQ
Why does the same “Hello” puzzle print different orders across runtimes?
Because task scheduling is host-defined. Each runtime (Node.js, Deno, Bun, browsers) implements its own event loop phases and decides when to drain different task queues (e.g., microtasks, nextTicks, immediates, timers). Those choices change the observable order of asynchronous operations.What’s the difference between the ECMAScript language, the engine, and the host runtime?
- Language (ECMAScript): the standardized rules and semantics (e.g., Promises, microtasks).- Engine (e.g., V8, SpiderMonkey, JavaScriptCore): parses, compiles, and executes the language.
- Host runtime (e.g., Node.js, Deno, browsers): embeds the engine, provides I/O, timers, networking, and determines scheduling behavior.
What are microtasks, nextTicks, and immediates, and how do they differ?
- Microtasks: language-defined queue (Promises, queueMicrotask). Runtimes choose when to drain it, often very frequently.- nextTick: Node.js-specific queue (process.nextTick), drained before other tasks in many Node.js scenarios.
- immediates: Node.js-specific queue (setImmediate), processed in a separate phase of the Node event loop. Their relative order vs. other queues varies by runtime and context.
Why does changing a file from .js (CommonJS) to .mjs (ESM) alter the output in Node.js?
Module systems affect evaluation and scheduling. Running as ESM changes when certain queues are drained and how the event loop turns at module load time, so the same code can interleave differently and produce a different order of outputs.Why doesn’t setTimeout(fn, 0) run first?
“Zero” delay still places the callback into the timers phase of the event loop. Other queues (like nextTicks or microtasks) may be drained before the timers phase runs, so callbacks scheduled with 0 ms aren’t guaranteed to execute first.Why did Deno error on setImmediate, and how can I fix it?
setImmediate is Node.js-specific and not a web standard. In Deno, it’s not global by default. You can import it viaimport { setImmediate } from "node:timers" (or use compatibility flags), but behavior may still differ from Node.What exactly is the event loop, and why does its design matter?
The event loop repeatedly checks for work (timers, I/O, user events) and runs queued tasks one at a time. Its design and phases differ across runtimes (e.g., browser UI responsiveness vs. Node’s OS I/O focus), which directly influences task ordering and performance characteristics.Should I rely on the exact ordering of asynchronous tasks?
Generally no. Precise ordering can vary by runtime, version, and even module system. Prefer writing code that doesn’t depend on subtle timing differences; when ordering is essential, enforce it explicitly (e.g., by chaining Promises or awaiting specific steps).What do “language-defined,” “implementation-defined,” and “host-defined” mean in practice?
- Language-defined: standardized, same observable behavior everywhere (e.g., Promises semantics).- Implementation-defined: specifics of a given engine family (e.g., V8 vs. SpiderMonkey optimizations).
- Host-defined: runtime-specific APIs/behavior (e.g., Node’s process.nextTick and setImmediate), which can differ widely.
JavaScript in Depth ebook for free