Node.js Asynchronous Programming
Most work in Node.js, like reading files or calling APIs, is asynchronous: it starts now and finishes later.
Synchronous vs Asynchronous
Synchronous code runs line by line, and each line waits for the one before it. Asynchronous code starts a task and continues without waiting. When the task is done, your code is told about the result.
Example
console.log("Order placed");
setTimeout(() => {
console.log("Pizza delivered (after 2 seconds)");
}, 2000);
console.log("Watching TV while waiting");Order placed Watching TV while waiting Pizza delivered (after 2 seconds)
Callbacks
The oldest style is the callback: a function you pass in, which runs when the task finishes. Node.js callbacks are error-first: the first argument is an error (or null), and the second is the result.
Example
const fs = require("fs");
fs.readFile("students.txt", "utf8", (err, data) => {
if (err) {
console.error("Could not read file:", err.message);
return;
}
console.log("File content:", data);
});When many callbacks are nested inside each other, code becomes hard to read. People call this callback hell.
Promises
A promise is an object that stands for a result that will arrive later. It is in one of three states:
| State | Meaning |
|---|---|
| Pending | Still working |
| Fulfilled | Finished with a value |
| Rejected | Failed with an error |
Use .then() for the result and .catch() for errors:
Example
import { readFile } from "node:fs/promises";
readFile("students.txt", "utf8")
.then((data) => console.log("Content:", data))
.catch((err) => console.error("Error:", err.message));You can also create your own promise:
Example
function checkFee(paid) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (paid) resolve("Fee received, welcome to class!");
else reject(new Error("Fee is pending"));
}, 1000);
});
}
checkFee(true).then((msg) => console.log(msg));Fee received, welcome to class!
async and await
async/await lets you write promise code that reads like normal, top-to-bottom code. This is the style most modern Node.js code uses.
Example
import { readFile } from "node:fs/promises";
async function showStudents() {
try {
const data = await readFile("students.txt", "utf8");
console.log("Students:");
console.log(data);
} catch (err) {
console.error("Something went wrong:", err.message);
}
}
showStudents();Students: Ali Ayesha Hamza
Rules to remember:
awaitpauses only this function until the promise finishes. Node.js keeps doing other work- You can use
awaitinside anasyncfunction, or at the top level of an ES module - Use
try/catchto handle errors from awaited promises
Running Tasks Together
If tasks do not depend on each other, start them all at once with Promise.all. It is faster than awaiting them one by one.
Example
import { readFile } from "node:fs/promises";
const [boys, girls] = await Promise.all([
readFile("boys.txt", "utf8"),
readFile("girls.txt", "utf8"),
]);
console.log("Both lists are loaded");If any of the promises fails, Promise.all fails too. Use Promise.allSettled when you want every result, even if some fail.
A Small Helper: sleep
Example
import { setTimeout as sleep } from "node:timers/promises";
console.log("Starting backup...");
await sleep(1500);
console.log("Backup finished");Starting backup... Backup finished
Forgetting await is a very common bug. Without it, you get a Promise { <pending> } object instead of the data. If you see that in your output, look for a missing await.
Exercise
Exercise
What does the await keyword do?
