Errors are not failures โ unhandled errors are. Production code differs from tutorial code mostly in how it handles the unhappy path.
The mechanics
try {
const data = JSON.parse(raw); // may throw
} catch (err) {
console.error(err.name, err.message); // SyntaxError, "Unexpected token..."
} finally {
spinner.hide(); // ALWAYS runs โ cleanup goes here
}Throw meaningful errors
function setMarks(m) {
if (m < 0 || m > 100) throw new RangeError(`Invalid marks: ${m}`);
}
class ApiError extends Error {
constructor(status, msg) { super(msg); this.name = "ApiError"; this.status = status; }
}What a good catch block does
- Shows the user something actionable ("Could not save โ retry?")
- Logs details for debugging
- Never swallows silently โ an empty catch is a future 3am bug hunt
Async errors
await inside try/catch catches rejected promises. Without await, use .catch() โ an unhandled rejection crashes Node processes.