Chapter 6 of 18 · Node.js for Beginners — Complete Free Tutorial
Asynchronous JavaScript in Node
Node's superpower and its biggest source of beginner confusion are the same thing: asynchrony. The Node.js for Beginners series now explains why Node never blocks on I/O, how callbacks, promises and async/await express "come back later", and how the event loop keeps one thread serving many requests. This chapter is the mental model the HTTP chapters depend on.
Blocking vs non-blocking
| Style | What happens while waiting | Example |
|---|---|---|
| Synchronous (blocking) | The whole thread sits still — no other request is served | fs.readFileSync() |
| Asynchronous (non-blocking) | The wait moves to the system; the thread keeps working | fs.readFile(), fetch() |
A server that blocks for 200 ms per request can handle about 5 requests per second per thread. A non-blocking server hands the wait to the OS and processes everything else in the meantime — that asymmetry is the entire business case for Node.
Callbacks: the original style
Asynchronous Node APIs take a function to call when the work finishes. Their convention is error-first: the callback's first argument is an error (or null), the second is the result.
const fs = require('node:fs');
fs.readFile('notes.txt', 'utf8', (err, data) => {
if (err) {
console.error('could not read:', err.code);
return;
}
console.log('file says:', data.trim());
});
console.log('this line runs first — the read is in flight');
Expected output:
this line runs first — the read is in flight
file says: remember to water the plants
Order matters: the last line prints first because readFile only schedules the work. Forgetting the if (err) guard is how "silent failure" bugs are born.
Promises and async/await
Promises wrap a future value; async/await lets you write that future as if it were ordinary sequential code. Modern Node APIs return promises — fs/promises, fetch, timers in node:timers/promises.
import { readFile } from 'node:fs/promises';
async function main() {
const data = await readFile('notes.txt', 'utf8');
console.log('file says:', data.trim());
const upper = data.toUpperCase();
console.log('shouty:', upper.trim());
}
main();
console.log('scheduled main() — runs after this line');
Expected output:
scheduled main() — runs after this line
file says: remember to water the plants
shouty: REMEMBER TO WATER THE PLANTS
awaitpauses only the surrounding async function, never the thread.- Errors become rejections — one
try/catchcovers everyawaitin the block. - Independent calls run in parallel with
Promise.all([...])instead of chained awaits.
Inside the event loop
When your stack empties, the loop drains its queues roughly in this order:
| Phase | What it handles |
|---|---|
| Timers | Due setTimeout and setInterval callbacks |
| Pending callbacks | I/O callbacks deferred from the previous cycle |
| Poll | Completed file and network events — where most time is spent |
| Check | setImmediate callbacks |
| Close | close events such as sockets shut down |
Microtasks — promise callbacks from await — are drained between every phase, which is why resolved promises run before any timer. You cannot see these phases directly, but knowing they exist explains the odd ordering you will occasionally observe: a setImmediate can beat a zero-delay setTimeout when the loop is already inside the check phase, and a promise callback always beats both.
Parallel, not concurrent-by-accident
const { readFile } = require('node:fs/promises');
async function readBoth() {
const start = Date.now();
// both reads are in flight at the same time
const [a, b] = await Promise.all([
readFile('a.txt', 'utf8'),
readFile('b.txt', 'utf8'),
]);
console.log('both in', Date.now() - start, 'ms');
return [a, b];
}
readBoth();
Expected output: something like both in 3 ms — roughly the time of the slower read, not the sum. Sequential awaits would add the durations; Promise.all starts everything first.
Common mistakes
- Blocking the loop — a 500 ms synchronous loop or
readFileSyncinside a request stalls every client. - Ignoring the error argument — error-first callbacks that skip the check swallow failures forever.
- Missing
await— you get a promise object instead of the value; symptoms areundefinedor[object Promise]. - Sequential awaits for independent work — chain latency unnecessarily; use
Promise.all. - Floats without handlers — an unhandled promise rejection prints a warning and, in current Node, crashes the process.
Practice checklist
- Reproduce the callback example and deliberately remove the error guard to see the failure mode.
- Rewrite a nested callback flow with
async/awaitand a singletry/catch. - Time three sequential
setTimeouts versusPromise.allwithnode:timers/promises. - Trigger an unhandled rejection and observe Node's exit behaviour.
- Add a synchronous busy-wait to a script and prove every later callback is delayed.
Asynchrony becomes concrete when the work is real: files on disk. Next: The File System Module.
Frequently Asked Questions
Node.js is a runtime that executes JavaScript outside the browser. It combines Google's V8 engine with APIs for files, networks, processes and streams, plus a package ecosystem (npm). You use it to build servers, command-line tools, APIs and anything that needs JavaScript running on a machine rather than in a page.
JavaScript is the language; Node.js is one environment that runs it. In the browser the language comes with a DOM, events and window APIs — in Node those are replaced by the file system, network and process modules. Syntax, functions, arrays, promises and async/await are identical, so your JavaScript knowledge transfers directly.
Browser code is sandboxed: it cannot read your files or open arbitrary ports. Node code has full machine access, which is why it is used for servers and tools but also why you never run untrusted code in it. There is no document or window in Node — the equivalents are global, process and modules.
npm is JavaScript's package manager and public registry. package.json describes your project: name, entry point, dependencies with version ranges, and scripts such as npm run dev. Installing a package copies it into node_modules and records the exact intent in package-lock.json so every machine installs identical versions.
Express is a minimal web framework for Node: a few thousand lines that give you routing, middleware, request parsing and static files on top of the raw HTTP server. It is the most common starting point for REST APIs and the backbone of this tutorial's project chapters.
Both work. CommonJS (require/module.exports) is the classic Node format; ES modules (import/export) are the standard and activated in a package.json with "type": "module". Modern projects favour ESM for browser compatibility, but you will read both in existing code, so this course teaches each.
Middleware are functions that run between the incoming request and the final response, in order: body parsing, logging, authentication, rate limits, error handling. Each receives the request, response and next function and can respond itself or pass control along — Express's pipeline model in one sentence.
A JSON file is perfect for learning CRUD: no setup, readable data, easy to reset. The moment you need concurrent writes, querying or multiple users, move to a real database — MySQL/PostgreSQL for relational data or MongoDB for documents. The storage chapter isolates persistence behind one module so swapping it later is straightforward.
A REST API exposes resources over HTTP using verbs on URLs: GET /api/todos lists, POST creates, PUT or PATCH updates, DELETE removes, and everything answers with JSON plus correct status codes (200, 201, 400, 404, 500). It is stateless — each request carries its own authentication — which makes it easy to cache, scale and consume from any frontend.
Node apps are long-running processes, not static files: deploy them to a Node-friendly host (a VPS, Render, Railway or Fly.io), keep secrets in environment variables, terminate HTTPS at a reverse proxy or platform edge, and send logs somewhere durable. The final chapter covers the full checklist including health checks and graceful shutdown.