Node.js Lesson 11: Async/Await & Error Handling
Async code is everywhere in Node.js — file reads, database queries, HTTP calls. Mastering async/await and proper error handling separates professional Node.js code from amateur code.
Async Evolution
// Old way: Callback Hell
readFile("a.txt", (err, a) => {
readFile("b.txt", (err, b) => {
writeFile("out.txt", a + b, (err) => {
console.log("Done! (maybe)");
});
});
});
// Modern way: async/await
async function combineFiles() {
const a = await readFile("a.txt", "utf8");
const b = await readFile("b.txt", "utf8");
await writeFile("out.txt", a + b);
console.log("Done!");
}
Error Handling Patterns
// Pattern 1: try/catch
async function fetchUser(id) {
try {
const user = await db.findUser(id);
return user;
} catch (err) {
console.error("Failed to fetch user:", err.message);
throw err; // re-throw to caller
}
}
// Pattern 2: Express async wrapper
const asyncHandler = (fn) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
app.get("/users/:id", asyncHandler(async (req, res) => {
const user = await db.findUser(req.params.id);
if (!user) return res.status(404).json({ error: "Not found" });
res.json(user);
}));
// Pattern 3: Global error handler
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: err.message || "Internal Server Error"
});
});
🏋️ Practice Task
Refactor your REST API (from lesson 10) to use async/await properly. Add an asyncHandler wrapper. Add a global error handler. Simulate async by wrapping data operations in setTimeout + Promise. Add specific error types (NotFoundError, ValidationError).
💡 Hint: class NotFoundError extends Error { constructor(msg) { super(msg); this.status = 404; } } throw new NotFoundError(“Task not found”) in routes.