JavaScript: JavaScript Errors and Debugging
Writing bug-free code is impossible — even the creator of JavaScript produced the typeof null === "object" bug in just 10 days. So learning to find bugs and handle errors is even more important than learning to write code.
1. Error Handling and Debugging
(1) Common Error Types
| Error Type | Cause | Example |
|---|---|---|
SyntaxError |
Invalid syntax | if (true { — mismatched parentheses |
ReferenceError |
Accessing a non-existent variable | console.log(notExist) |
TypeError |
Operating on the wrong type | undefined.toString() |
RangeError |
Value out of valid range | new Array(-1) |
SyntaxError is caught before code execution (parsing phase). The other three are runtime errors.
(2) try...catch...finally
Use try...catch to catch runtime errors and prevent your program from crashing:
<script>
try {
const data = JSON.parse('{bad json}');
} catch (error) {
console.log("Parse failed:", error.message);
} finally {
console.log("Cleanup work");
}
</script>
The error object in catch has two commonly used properties:
error.message— a description of the errorerror.name— the error type name
(3) throw — Manually Throwing Errors
try...catch can only catch runtime errors, but business logic errors need to be thrown manually:
<script>
function setAge(age) {
if (typeof age !== "number" || age < 0) {
throw new Error("Age must be a positive number");
}
console.log("Age set to: " + age);
}
try {
setAge(-5);
} catch (e) {
console.log(e.message);
}
</script>
throw can throw any value, but using new Error() or its subclasses is recommended — they include stack trace information.
(4) Custom Error Messages
Use subclasses of Error or custom properties to provide richer error information:
<script>
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
try {
throw new ValidationError("email", "Invalid email format");
} catch (e) {
console.log(e.name + " [" + e.field + "]: " + e.message);
}
</script>
(5) Browser DevTools (F12)
| Panel | Purpose |
|---|---|
| Console | View logs, execute code, see errors |
| Sources | Set breakpoints, step through code, inspect variables |
| Network | Inspect network requests, response status, load times |
| Elements | View/modify DOM and styles |
Breakpoint debugging is the most powerful debugging technique: click a line number in the Sources panel to set a breakpoint, refresh the page, and the code pauses at that point — step through line by line, inspect variables, and examine the call stack.
(6) The console Family
| Method | Purpose |
|---|---|
console.log() |
General output |
console.warn() |
Warning (yellow) |
console.error() |
Error (red) |
console.table() |
Display arrays/objects as a table |
console.time() / console.timeEnd() |
Timing |
console.group() / console.groupEnd() |
Grouped output |
console.clear() |
Clear the console |
console.table is especially useful for displaying arrays of objects — far more readable than log.
(7) Debugging Tips
- Comment out halves: Comment out half the code, see which half has the problem, and narrow it down step by step
- console.log everywhere: Print variable values at key points — primitive but effective
- Breakpoint debugging: More powerful than
console.log— pause execution and modify variables in real time - Rubber duck debugging: Explain your code line by line to a rubber duck (or a colleague). Often, you'll find the bug halfway through your explanation.
▶ Example: try/catch for JSON Parsing
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSON Parsing</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.demo { max-width: 600px; margin: 16px auto; }
textarea { width: 100%; height: 120px; padding: 12px; font-size: 14px; font-family: monospace; border: 2px solid #ccc; border-radius: 6px; resize: vertical; box-sizing: border-box; }
button { padding: 10px 24px; font-size: 16px; border: none; border-radius: 6px; cursor: pointer; margin: 8px 4px; }
.parse { background: #4a90d9; color: #fff; }
.parse:hover { background: #357abd; }
.bad { background: #d9534f; color: #fff; }
.bad:hover { background: #c9302c; }
.result { margin-top: 16px; padding: 16px; border-radius: 8px; }
.success { background: #d4edda; border: 2px solid #5cb85c; }
.error-box { background: #f8d7da; border: 2px solid #d9534f; }
.error-type { font-weight: bold; color: #721c24; }
.error-msg { margin-top: 4px; color: #721c24; }
</style>
</head>
<body>
<h2 style="text-align:center;">JSON Parse Error Handling</h2>
<div class="demo">
<textarea id="jsonInput">{
"name": "Alice",
"age": 20,
"skills": ["JavaScript", "HTML", "CSS"]
}</textarea>
<div style="text-align:center;">
<button class="parse" id="parseBtn">Parse JSON</button>
<button class="bad" id="badBtn">Insert Invalid JSON</button>
</div>
<div id="result"></div>
</div>
<script>
function parseJSON(jsonStr) {
try {
const data = JSON.parse(jsonStr);
return { success: true, data: data };
} catch (error) {
return { success: false, name: error.name, message: error.message };
}
}
document.getElementById("parseBtn").addEventListener("click", function() {
var input = document.getElementById("jsonInput").value;
var result = parseJSON(input);
var output = document.getElementById("result");
if (result.success) {
output.className = "result success";
output.innerHTML = "<strong>Parse successful!</strong><pre>" +
JSON.stringify(result.data, null, 2) + "</pre>";
} else {
output.className = "result error-box";
output.innerHTML = '<div class="error-type">' + result.name +
'</div><div class="error-msg">' + result.message + '</div>';
}
});
document.getElementById("badBtn").addEventListener("click", function() {
document.getElementById("jsonInput").value = '{name: "Alice", age: 20}';
});
</script>
</body>
</html>
▶ Example: Custom Errors and Form Validation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom Errors</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.form { max-width: 440px; margin: 16px auto; padding: 24px; background: #f9f9f9; border-radius: 12px; }
.row { margin: 12px 0; }
label { display: block; margin-bottom: 4px; font-weight: bold; }
input { width: 100%; padding: 10px 12px; font-size: 16px; border: 2px solid #ccc; border-radius: 6px; box-sizing: border-box; }
input.valid { border-color: #5cb85c; }
input.invalid { border-color: #d9534f; }
.field-error { color: #d9534f; font-size: 13px; margin-top: 2px; min-height: 20px; }
button { padding: 10px 24px; font-size: 16px; border: none; border-radius: 6px; cursor: pointer; background: #4a90d9; color: #fff; width: 100%; margin-top: 8px; }
button:hover { background: #357abd; }
.global-msg { margin-top: 12px; padding: 12px; border-radius: 6px; text-align: center; }
.global-ok { background: #d4edda; color: #155724; }
.global-fail { background: #f8d7da; color: #721c24; }
</style>
</head>
<body>
<h2 style="text-align:center;">Custom Error Validation</h2>
<div class="form">
<div class="row">
<label>Username (3–20 characters):</label>
<input type="text" id="username" placeholder="Enter username" />
<div class="field-error" id="usernameErr"></div>
</div>
<div class="row">
<label>Age (0–150):</label>
<input type="text" id="age" placeholder="Enter age" />
<div class="field-error" id="ageErr"></div>
</div>
<div class="row">
<label>Email:</label>
<input type="text" id="email" placeholder="Enter email" />
<div class="field-error" id="emailErr"></div>
</div>
<button id="submitBtn">Submit</button>
<div class="global-msg" id="globalMsg"></div>
</div>
<script>
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
function validateUsername(value) {
if (!value.trim()) throw new ValidationError("username", "Username cannot be empty");
if (value.trim().length < 3) throw new ValidationError("username", "Username must be at least 3 characters");
if (value.trim().length > 20) throw new ValidationError("username", "Username must be at most 20 characters");
}
function validateAge(value) {
if (!value.trim()) throw new ValidationError("age", "Age cannot be empty");
var num = Number(value);
if (isNaN(num)) throw new ValidationError("age", "Please enter a valid number");
if (num < 0 || num > 150) throw new ValidationError("age", "Age range: 0–150");
if (!Number.isInteger(num)) throw new ValidationError("age", "Age must be an integer");
}
function validateEmail(value) {
if (!value.trim()) throw new ValidationError("email", "Email cannot be empty");
if (!value.includes("@")) throw new ValidationError("email", "Email must contain @");
if (!value.includes(".")) throw new ValidationError("email", "Invalid email format");
}
var validators = {
username: validateUsername,
age: validateAge,
email: validateEmail
};
function clearErrors() {
["username", "age", "email"].forEach(function(field) {
document.getElementById(field).className = "";
document.getElementById(field + "Err").textContent = "";
});
document.getElementById("globalMsg").innerHTML = "";
document.getElementById("globalMsg").className = "global-msg";
}
document.getElementById("submitBtn").addEventListener("click", function() {
clearErrors();
var hasError = false;
["username", "age", "email"].forEach(function(field) {
var value = document.getElementById(field).value;
try {
validators[field](value);
document.getElementById(field).className = "valid";
} catch (e) {
hasError = true;
document.getElementById(field).className = "invalid";
document.getElementById(field + "Err").textContent =
e.name + ": " + e.message;
}
});
var msg = document.getElementById("globalMsg");
if (hasError) {
msg.className = "global-msg global-fail";
msg.textContent = "Form has errors — please fix and retry";
} else {
msg.className = "global-msg global-ok";
msg.textContent = "All fields passed validation!";
}
});
</script>
</body>
</html>
▶ Example: Error Types Quick Reference and console Methods
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Error Types & Console</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.section { margin: 20px 0; }
table { border-collapse: collapse; width: 100%; max-width: 700px; }
td, th { border: 1px solid #ddd; padding: 10px 14px; text-align: left; }
th { background: #4a90d9; color: #fff; }
.trigger { background: #f8d7da; }
.safe { background: #d4edda; }
button { padding: 6px 16px; border: none; border-radius: 4px; cursor: pointer; margin: 2px; font-size: 14px; }
.btn-err { background: #d9534f; color: #fff; }
.btn-safe { background: #5cb85c; color: #fff; }
.btn-info { background: #5bc0de; color: #fff; }
.output { margin-top: 12px; padding: 12px; background: #1a1a2e; color: #e0e0e0; border-radius: 6px; font-family: monospace; font-size: 13px; min-height: 60px; max-height: 200px; overflow-y: auto; }
.log-line { margin: 2px 0; }
.log-warn { color: #f0ad4e; }
.log-error { color: #d9534f; }
.log-info { color: #5bc0de; }
</style>
</head>
<body>
<h2>Error Types & Console Methods Demo</h2>
<div class="section">
<h3>Common Error Types</h3>
<table>
<tr><th>Error Type</th><th>Trigger Code</th><th>try/catch Result</th></tr>
<tr class="trigger"><td>SyntaxError</td><td><code>JSON.parse("{bad")</code></td><td id="r1"></td></tr>
<tr class="trigger"><td>ReferenceError</td><td><code>notExistVar</code></td><td id="r2"></td></tr>
<tr class="trigger"><td>TypeError</td><td><code>undefined.toString()</code></td><td id="r3"></td></tr>
<tr class="trigger"><td>RangeError</td><td><code>new Array(-1)</code></td><td id="r4"></td></tr>
</table>
</div>
<div class="section">
<h3>console Methods (Simulated Output)</h3>
<button class="btn-info" onclick="simLog('log', 'Regular log')">console.log</button>
<button class="btn-info" onclick="simLog('warn', 'Warning message')">console.warn</button>
<button class="btn-err" onclick="simLog('error', 'Error message')">console.error</button>
<button class="btn-info" onclick="simLog('info', 'Info message')">console.info</button>
<button class="btn-safe" onclick="simTable()">console.table</button>
<button class="btn-safe" onclick="simTime()">console.time</button>
<button class="btn-info" onclick="clearSim()">Clear</button>
<div class="output" id="simConsole"></div>
</div>
<script>
var tests = [
function() { JSON.parse("{bad"); },
function() { notExistVar; },
function() { var x; x.toString(); },
function() { new Array(-1); }
];
var ids = ["r1", "r2", "r3", "r4"];
var names = ["SyntaxError", "ReferenceError", "TypeError", "RangeError"];
tests.forEach(function(fn, i) {
try {
fn();
document.getElementById(ids[i]).textContent = "Not triggered (unexpected)";
} catch (e) {
document.getElementById(ids[i]).innerHTML =
'<span class="safe">' + e.name + ": " + e.message + '</span>';
}
});
function simLog(type, msg) {
var cls = type === "warn" ? "log-warn" :
type === "error" ? "log-error" : "log-info";
var prefix = type === "warn" ? "⚠" :
type === "error" ? "✖" : "ℹ";
addLine(prefix + " " + msg, cls);
if (type !== "error") {
// Visual output shown via addLine above
}
}
function simTable() {
var data = [
{ name: "Alice", age: 20, score: 90 },
{ name: "Bob", age: 22, score: 85 },
{ name: "Charlie", age: 21, score: 95 }
];
addLine("console.table:", "log-info");
data.forEach(function(d) {
addLine(" " + d.name + " | " + d.age + " | " + d.score, "log-info");
});
// Visual output shown above
}
function simTime() {
console.time("demo");
var sum = 0;
for (var i = 0; i < 1000000; i++) { sum += i; }
console.timeEnd("demo");
addLine("⏱ console.time/timeEnd: 1,000,000 additions done", "log-info");
}
function addLine(text, cls) {
var div = document.getElementById("simConsole");
div.innerHTML += '<div class="log-line ' + (cls || "") + '">' + text + '</div>';
div.scrollTop = div.scrollHeight;
}
function clearSim() {
document.getElementById("simConsole").innerHTML = "";
}
</script>
</body>
</html>
❓ FAQ
SyntaxError is thrown during the parsing phase before execution even begins, so the try block is never reached. However, if a SyntaxError occurs at runtime (e.g. JSON.parse fails, or new Function("bad syntax")), it can be caught.catch alone is sufficient.return is for normal flow — the caller handles the returned value. throw is for abnormal flow — it skips subsequent code and walks up the call stack looking for a catch. Use throw for errors, return for normal results.📖 Summary
try/catchcatches runtime errors;finallyalways runs, perfect for cleanupthrowraises errors manually — preferthrow new Error("message")over strings or numbers- Error objects have
nameandmessageproperties; the browser console shows the full stack trace - DevTools (F12): Sources panel for breakpoints, Watch for variables, step-through execution
- Console methods:
log/warn/error/table/group/time/timeEndboost debugging efficiency
📝 Exercises
- Write a function
safeDivide(a, b)that uses try...catch to handle division by zero (hint: dividing by zero in JS doesn't throw — you need to check and throw yourself). Return the result or an error message. - Write a function
safeJSONParse(str)that parses a JSON string. On failure, return{ success: false, error: <error message> }. On success, return{ success: true, data: <parsed result> }. - Write a function
retry(fn, times): executefn, and if it throws, retry up totimestimes. If all attempts fail, throw the last error. Implement with try...catch.