Lesson 13 of 18 · 0 of 2 exercises solved
Error Handling & Debugging
You will learn how to catch and throw , including errors from code. Handling errors well keeps a program from failing silently or crashing.
// Error types
try {
null.property // TypeError
} catch (e) {
console.log(e instanceof TypeError) // true
console.log(e.name) // 'TypeError'
console.log(e.message) // Cannot read properties of null
console.log(e.stack) // full stack trace
}
// Custom errors
class ValidationError extends Error {
constructor(field, message) {
super(message)
this.name = "ValidationError"
this.field = field
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message)
this.name = "NetworkError"
this.statusCode = statusCode
}
}
function validateAge(age) {
if (typeof age !== "number") throw new ValidationError("age", "must be a number")
if (age < 0 || age > 150) throw new ValidationError("age", "must be 0-150")
return true
}
try {
validateAge(-5)
} catch (e) {
if (e instanceof ValidationError) {
console.log(`Field "${e.field}" failed: ${e.message}`)
} else {
throw e // re-throw unexpected errors
}
}// Async error handling
// Three patterns: callbacks (old), Promises, async/await
// 1. Promise catch
fetch("https://api.example.com/data")
.then(res => {
if (!res.ok) throw new NetworkError("Request failed", res.status)
return res.json()
})
.then(data => console.log(data))
.catch(err => {
if (err instanceof NetworkError) {
console.error(`HTTP ${err.statusCode}: ${err.message}`)
} else {
console.error("Unexpected error:", err)
}
})
.finally(() => console.log("Request complete"))
// 2. async/await try/catch (preferred)
async function fetchUser(id) {
try {
const res = await fetch(`/api/users/${id}`)
if (!res.ok) throw new NetworkError("Not found", res.status)
return await res.json()
} catch (err) {
if (err instanceof NetworkError && err.statusCode === 404) {
return null // user not found — not an error per se
}
throw err // re-throw everything else
}
}
// Handling multiple async operations
async function loadDashboard(userId) {
const results = await Promise.allSettled([
fetchUser(userId),
fetch("/api/notifications").then(r => r.json()),
fetch("/api/stats").then(r => r.json()),
])
for (const [i, result] of results.entries()) {
if (result.status === "rejected") {
console.error(`Request ${i} failed:`, result.reason)
}
}
return results
.filter(r => r.status === "fulfilled")
.map(r => r.value)
}Common question
Does catching every error make a program robust?
Errors in JavaScript are objects with a name, a message and a . throw raises one; try/catch/finally handles it; async errors travel as rejected and have to be awaited or caught with .catch. Your own subclasses of Error let handlers tell "the user typed a bad date" apart from "the is down".
Handle errors at the level that can do something about them, log or rethrow the rest, and never leave a catch block empty. Read stack traces from the top frame down, use the debugger and breakpoints for tangled state, and once you find the failing case, write it as a test.
Source: An error that is caught and ignored has not been handled, only hidden. The Node.js documentation swallowing errors among "the most common mistakes".
Common mix-ups
Each of these sounds reasonable. Think about why it is wrong, then open the explanation.
Wrapping everything in try/catch prevents crashes, and a program that never crashes is robust.
try/catch around an async call catches errors from the promise.
Throwing is fine, because catch receives whatever was thrown.
Practice
Write parseJsonSafe(text) that never throws: it returns { ok: true, value } when the parses, and { ok: false, error } with the error message when it does not.
A result object instead of a throw
function parseJsonSafe(text) {
return { ok: true, value: JSON.parse(text) }
}
console.log(parseJsonSafe('{"a": 1}'))
console.log(parseJsonSafe('{nope'))
Press Run to try your code, then Check answer to grade it.Create ValidationError, a subclass of Error that records which field failed, and validateAge(age) that throws it when age is not an integer between 0 and 150. The error's name should be "ValidationError".
A custom error class
class ValidationError extends Error {
constructor(message, field) {
super(message)
}
}
function validateAge(age) {
return age
}
try {
validateAge(-4)
} catch (err) {
console.log(err.name, err.field, err.message)
}
Press Run to try your code, then Check answer to grade it.Check yourself
Why is an empty catch block, catch (e) {}, considered dangerous?
Tip: use the ← and → keys to move between lessons.