Stop crashes and respond to failure gracefully. Learn try/catch, handling errors in async code and callbacks, custom error classes, and last-resort process handlers.
Why: code that might fail goes in the try block; if it throws, control jumps to catch where you handle it; finally always runs (success or failure) and is handy for cleanup. This keeps one failure from crashing the whole program.
// app.js
try {
const data = JSON.parse('{ broken json') // replace '{ broken json' with '{ "broken": "json" }'
console.log(data)
} catch (err) {
console.error('Invalid JSON:', err.message)
} finally {
console.log('Done trying.')
}Why: an awaited Promise that fails throws an error, so the same try/catch works. Always wrap awaits that can fail — an unhandled rejected Promise can crash your app.
// app.js
import { readFile } from "node:fs/promises";
async function load() {
try {
return await readFile("note.txt", "utf8");
} catch (err) {
console.error("Could not load:", err.message);
return ""; // a safe fallback value
}
}
console.log(await load());Why: older callback-style APIs pass the error as the FIRST argument (null if all went well) and the result second. Always check the error before using the result.
// app.js
import { readFile } from 'node:fs'
readFile('data.txt', 'utf8', (err, data) => {
if (err) {
// handle and stop — do not fall through
console.error('Read failed:', err.message)
return
}
console.log(data)
})Why: making your own Error subclass lets you attach extra info (like an HTTP status code) and tell error types apart with instanceof. This makes error handling in bigger apps much cleaner.
// app.js
class NotFoundError extends Error {
constructor(message) {
super(message)
this.name = 'NotFoundError'
this.statusCode = 404
}
}
try {
throw new NotFoundError('User does not exist')
} catch (err) {
if (err instanceof NotFoundError) {
console.log(err.statusCode, err.message) // 404 ...
}
}