Working Asynchronously
In the last chapter, we saw how Node.js reads files without freezing the program. This chapter walks through the three styles you can use to write that kind of code: callbacks (functions you hand over to be called later), promises (objects representing a future result), and async/await (syntax that makes async code look normal).
What to have ready: You should have Node.js installed, know how to run a file with node filename.js, and have seen a file read with the fs module. No other background is needed.
Why asynchronous code exists
Imagine a restaurant with one waiter. A synchronous waiter takes one order, goes to the kitchen, waits for the food, delivers it, and only then takes the next order. Everyone else starves. An asynchronous waiter takes an order, hands it to the kitchen, and serves other tables while the food cooks. When the kitchen rings the bell, the waiter delivers that meal.
Computers work the same way. Reading a file or fetching a web page takes far longer than doing math. Node.js starts the slow task, keeps running other code, and comes back when the task finishes.
Style 1: Callbacks
A callback is a function you pass to another function, to be run later.
const { readFile } = require('node:fs');
readFile('notes.txt', 'utf8', (err, text) => {
if (err) {
console.error('Could not read file:', err.message);
return;
}
console.log(text);
});
console.log('This prints first!');The readFile call returns immediately. The message at the bottom prints before the file content. If several tasks each need the result of the previous one, callbacks nest inside callbacks. Many levels deep, this becomes hard to read — a problem developers famously called "callback hell."
Style 2: Promises
A promise is an object that stands for a value that does not exist yet. It is like a receipt number at a shop: you can't take the item home yet, but you will be able to.
Promises can be chained with .then(), which avoids deep nesting:
const { readFile } = require('node:fs/promises');
readFile('notes.txt', 'utf8')
.then((text) => console.log(text))
.catch((err) => console.error('Could not read file:', err.message));.catch() runs if the task failed — like the shop telling you the item is out of stock.
Style 3: async/await
The async and await keywords let you write promise-based code that reads top to bottom, like ordinary code:
const { readFile } = require('node:fs/promises');
async function main() {
try {
const text = await readFile('notes.txt', 'utf8');
console.log(text);
} catch (err) {
console.error('Could not read file:', err.message);
}
}
main();await means "pause this function until the result arrives." The keyword only works inside a function marked async, which makes the function return a promise. A try/catch block handles failures, the same way you would handle errors in regular code.
This is the style most modern Node.js code uses, and the one we recommend.
A common mistake: forgetting await
const { readFile } = require('node:fs/promises');
async function main() {
const text = readFile('notes.txt', 'utf8'); // missing await!
console.log(text); // prints [object Promise], not the file
}
main();Without await, you get the receipt, not the item. The variable holds a promise object, not the text. If your output looks like [object Promise], a missing await is the first thing to check.
Show diagram source
flowchart TD
A[main starts] --> B[await readFile]
B --> C[Function pauses]
C --> D[Event loop runs other work]
D --> E[File read completes]
E --> F[Function resumes]
F --> G[Print text]Exercise
Why does console.log('This prints first!') run before the file content in the callback example?
Show answer
Because readFile starts the read and returns immediately without waiting. The next line of code runs right away, and the callback only runs later, once the file has been read.
Exercise
You write const data = readFile('notes.txt', 'utf8'); inside an async function and print data, but you see [object Promise]. What is wrong and how do you fix it?
Show answer
The await keyword is missing. Write const data = await readFile('notes.txt', 'utf8'); so the function pauses until the file content is ready.
Spotted a mistake, or code that does not work?