Chapter 6 of 12
Node.js was built around asynchronous, non-blocking I/O. Understanding why this matters β and how async patterns evolved from callbacks to promises to async/await β makes you a much stronger Node developer.
ANALOGY
A restaurant kitchen, not a food stall. A food stall serves one customer at a time: take order, cook, serve, then next. A restaurant kitchen takes many orders, cooks them in parallel, and serves when each is ready. Node.js is the kitchen β it starts many I/O operations and handles results as they come in, without waiting for each to finish before starting the next.
// 1. CALLBACKS β the original approach (hard to read, error-prone)
fs.readFile("users.json", "utf8", (err, data) => {
if (err) return handleError(err);
const users = JSON.parse(data);
db.save(users, (err, result) => {
if (err) return handleError(err);
sendEmail(result.ids, (err) => {
if (err) return handleError(err);
console.log("Done!");
});
});
});
// The "pyramid of doom" β each callback indents further
// 2. PROMISES β flattened with .then() chains
readFilePromise("users.json", "utf8")
.then(data => JSON.parse(data))
.then(users => db.save(users))
.then(result => sendEmail(result.ids))
.then(() => console.log("Done!"))
.catch(err => handleError(err));
// 3. ASYNC/AWAIT β reads like synchronous code
async function processUsers() {
try {
const data = await fs.promises.readFile("users.json", "utf8");
const users = JSON.parse(data);
const result = await db.save(users);
await sendEmail(result.ids);
console.log("Done!");
} catch (err) {
handleError(err);
}
}// Sequential β each waits for the previous (slow)
const users = await fetchUsers();
const courses = await fetchCourses();
const stats = await fetchStats();
// Total time = time(users) + time(courses) + time(stats)
// Parallel β all fire at once (fast)
const [users, courses, stats] = await Promise.all([
fetchUsers(),
fetchCourses(),
fetchStats(),
]);
// Total time = max(time(users), time(courses), time(stats))
// Promise.allSettled β runs all, even if some fail
const results = await Promise.allSettled([fetchA(), fetchB()]);
results.forEach(r => {
if (r.status === "fulfilled") use(r.value);
else console.error(r.reason);
});