JavaScript Error Handling and Debugging
1. Error Types (Error, TypeError, ReferenceError, SyntaxError) and Properties
Built-in Error Types
| Error Type | Description | Common Causes |
|---|---|---|
| Error | Generic error (base class) | Manually thrown errors |
| SyntaxError | Invalid JavaScript syntax | Parse errors, malformed code |
| ReferenceError | Invalid reference to variable | Undefined variables, accessing before declaration |
| TypeError | Value is not expected type | Calling non-function, accessing property on null/undefined |
| RangeError | Value not in allowed range | Invalid array length, recursion limit |
| URIError | Invalid URI encoding/decoding | encodeURI/decodeURI with malformed URI |
| EvalError | Error in eval() (legacy) | Rarely used in modern JavaScript |
| AggregateError ES2021 | Multiple errors wrapped together | Promise.any() rejection, multiple failures |
Error Object Properties
| Property | Description | Standard |
|---|---|---|
| name | Error type name (e.g., "TypeError") | ✓ Standard |
| message | Human-readable error description | ✓ Standard |
| stack | Stack trace (non-standard but widely supported) | ✗ Non-standard |
| cause ES2022 | Underlying error that caused this error | ✓ Standard |
| fileName | File where error occurred (Firefox) | ✗ Non-standard |
| lineNumber | Line number where error occurred (Firefox) | ✗ Non-standard |
| columnNumber | Column number where error occurred (Firefox) | ✗ Non-standard |
Error Constructor Options
| Constructor | Syntax | Parameters |
|---|---|---|
| new Error() | new Error(message, options) |
message: string, options: {cause} |
| message | Error description | String |
| options.cause | Original error that caused this error | Any (typically Error object) |
Example: Built-in error types
// SyntaxError (parse-time error, not catchable at runtime)
// eval('var x = ;'); // SyntaxError: Unexpected token ';'
// ReferenceError
try {
console.log(nonExistentVariable);
} catch (e) {
console.log(e.name); // "ReferenceError"
console.log(e.message); // "nonExistentVariable is not defined"
}
// TypeError
try {
const obj = null;
obj.someMethod();
} catch (e) {
console.log(e.name); // "TypeError"
console.log(e.message); // "Cannot read properties of null"
}
try {
const notAFunction = 42;
notAFunction();
} catch (e) {
console.log(e.name); // "TypeError"
console.log(e.message); // "notAFunction is not a function"
}
// RangeError
try {
const arr = new Array(-1);
} catch (e) {
console.log(e.name); // "RangeError"
console.log(e.message); // "Invalid array length"
}
function recursiveFunction() {
recursiveFunction();
}
try {
recursiveFunction();
} catch (e) {
console.log(e.name); // "RangeError"
console.log(e.message); // "Maximum call stack size exceeded"
}
// URIError
try {
decodeURIComponent('%');
} catch (e) {
console.log(e.name); // "URIError"
console.log(e.message); // "URI malformed"
}
// AggregateError (ES2021)
const errors = [
new Error('Error 1'),
new Error('Error 2'),
new Error('Error 3')
];
const aggregateError = new AggregateError(errors, 'Multiple errors occurred');
console.log(aggregateError.name); // "AggregateError"
console.log(aggregateError.message); // "Multiple errors occurred"
console.log(aggregateError.errors); // Array of 3 errors
aggregateError.errors.forEach((err, i) => {
console.log(`Error ${i + 1}:`, err.message);
});
Example: Error object properties
// Creating error manually
const error = new Error('Something went wrong');
console.log(error.name); // "Error"
console.log(error.message); // "Something went wrong"
console.log(error.stack); // Stack trace (multi-line string)
// Stack trace example
function a() {
b();
}
function b() {
c();
}
function c() {
throw new Error('Error in function c');
}
try {
a();
} catch (e) {
console.log(e.stack);
// Error: Error in function c
// at c (file.js:10:11)
// at b (file.js:6:5)
// at a (file.js:2:5)
// at file.js:14:5
}
// Error cause (ES2022)
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (originalError) {
// Wrap original error with context
throw new Error('Failed to fetch data', {
cause: originalError
});
}
}
try {
await fetchData('https://invalid-url');
} catch (error) {
console.log(error.message); // "Failed to fetch data"
console.log(error.cause); // Original fetch error
console.log(error.cause.message); // Original error message
}
// Error with custom properties
const customError = new Error('Custom error');
customError.code = 'ERR_CUSTOM';
customError.statusCode = 400;
customError.timestamp = Date.now();
console.log(customError.code); // "ERR_CUSTOM"
console.log(customError.statusCode); // 400
console.log(customError.timestamp); // Unix timestamp
Example: Parsing stack traces
// Parse stack trace for debugging
function parseStackTrace(error) {
if (!error.stack) return [];
const lines = error.stack.split('\n');
const frames = [];
// Skip first line (error message)
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
// Example format: "at functionName (file.js:10:5)"
const match = line.match(/at (.+?) \((.+?):(\d+):(\d+)\)/);
if (match) {
frames.push({
functionName: match[1],
fileName: match[2],
lineNumber: parseInt(match[3], 10),
columnNumber: parseInt(match[4], 10)
});
}
}
return frames;
}
try {
throw new Error('Test error');
} catch (e) {
const frames = parseStackTrace(e);
console.log('Stack frames:', frames);
// Print formatted stack
frames.forEach((frame, i) => {
console.log(
` ${i + 1}. ${frame.functionName}`,
`at ${frame.fileName}:${frame.lineNumber}:${frame.columnNumber}`
);
});
}
// Capture stack trace programmatically
function captureStack() {
const obj = {};
Error.captureStackTrace(obj, captureStack);
return obj.stack;
}
console.log(captureStack());
Key Points: JavaScript has 8 built-in error types. All errors
have
name and message properties. stack property (non-standard but
universal) provides call stack. ES2022 cause option enables error
chaining.
AggregateError (ES2021) wraps multiple errors.
2. Custom Error Classes and Error Inheritance
Custom Error Pattern
| Step | Requirement | Implementation |
|---|---|---|
| 1. Extend Error | Inherit from Error class | class CustomError extends Error |
| 2. Set name | Set error name to class name | this.name = this.constructor.name |
| 3. Call super() | Initialize Error with message | super(message) |
| 4. Capture stack | Fix stack trace (optional) | Error.captureStackTrace(this, this.constructor) |
| 5. Add properties | Add custom data | this.code = code |
Common Custom Error Types
| Error Type | Use Case | Additional Properties |
|---|---|---|
| ValidationError | Input validation failures | field, value, rule |
| NotFoundError | Resource not found (404) | resourceType, resourceId |
| UnauthorizedError | Authentication failure (401) | userId, reason |
| ForbiddenError | Authorization failure (403) | action, resource |
| NetworkError | Network/HTTP failures | statusCode, url, method |
| TimeoutError | Operation timeout | duration, operation |
| DatabaseError | Database operation failures | query, code |
Example: Basic custom error class
// Simple custom error
class CustomError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
// Maintains proper stack trace (V8 only)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
// Usage
try {
throw new CustomError('Something went wrong');
} catch (e) {
console.log(e instanceof CustomError); // true
console.log(e instanceof Error); // true
console.log(e.name); // "CustomError"
console.log(e.message); // "Something went wrong"
}
// Custom error with additional properties
class ValidationError extends Error {
constructor(message, field, value) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.value = value;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
function validateAge(age) {
if (typeof age !== 'number') {
throw new ValidationError(
'Age must be a number',
'age',
age
);
}
if (age < 0 || age > 150) {
throw new ValidationError(
'Age must be between 0 and 150',
'age',
age
);
}
return true;
}
try {
validateAge('twenty');
} catch (e) {
if (e instanceof ValidationError) {
console.log(`Validation failed for field "${e.field}"`);
console.log(`Invalid value: ${e.value}`);
console.log(`Error: ${e.message}`);
}
}
try {
validateAge(200);
} catch (e) {
console.log(e.field); // "age"
console.log(e.value); // 200
console.log(e.message); // "Age must be between 0 and 150"
}
Example: Domain-specific error classes
// HTTP error hierarchy
class HttpError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'HttpError';
this.statusCode = statusCode;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
class NotFoundError extends HttpError {
constructor(resource, id) {
super(`${resource} with id ${id} not found`, 404);
this.name = 'NotFoundError';
this.resource = resource;
this.id = id;
}
}
class UnauthorizedError extends HttpError {
constructor(message = 'Unauthorized') {
super(message, 401);
this.name = 'UnauthorizedError';
}
}
class ForbiddenError extends HttpError {
constructor(action, resource) {
super(`Not allowed to ${action} ${resource}`, 403);
this.name = 'ForbiddenError';
this.action = action;
this.resource = resource;
}
}
// Usage
async function getUser(userId) {
const user = await db.findUser(userId);
if (!user) {
throw new NotFoundError('User', userId);
}
return user;
}
async function deletePost(userId, postId) {
const post = await db.findPost(postId);
if (!post) {
throw new NotFoundError('Post', postId);
}
if (post.authorId !== userId) {
throw new ForbiddenError('delete', 'post');
}
await db.deletePost(postId);
}
// Error handling
try {
await getUser(999);
} catch (e) {
if (e instanceof NotFoundError) {
console.log(`Status: ${e.statusCode}`);
console.log(`${e.resource} ${e.id} not found`);
} else if (e instanceof HttpError) {
console.log(`HTTP Error ${e.statusCode}: ${e.message}`);
}
}
Example: Advanced error patterns
// Error with error codes
class ApplicationError extends Error {
constructor(message, code, details = {}) {
super(message);
this.name = 'ApplicationError';
this.code = code;
this.details = details;
this.timestamp = new Date().toISOString();
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
toJSON() {
return {
name: this.name,
message: this.message,
code: this.code,
details: this.details,
timestamp: this.timestamp
};
}
}
// Error factory
class ErrorFactory {
static validation(field, message) {
return new ApplicationError(
message,
'VALIDATION_ERROR',
{field}
);
}
static notFound(resource, id) {
return new ApplicationError(
`${resource} not found`,
'NOT_FOUND',
{resource, id}
);
}
static network(url, statusCode) {
return new ApplicationError(
'Network request failed',
'NETWORK_ERROR',
{url, statusCode}
);
}
}
try {
throw ErrorFactory.validation('email', 'Invalid email format');
} catch (e) {
console.log(e.code); // "VALIDATION_ERROR"
console.log(e.details.field); // "email"
console.log(JSON.stringify(e)); // Full error as JSON
}
// Async error wrapper
class AsyncOperationError extends Error {
constructor(message, operation, originalError) {
super(message);
this.name = 'AsyncOperationError';
this.operation = operation;
this.originalError = originalError;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
async function wrapAsync(operation, fn) {
try {
return await fn();
} catch (error) {
throw new AsyncOperationError(
`Failed to ${operation}`,
operation,
error
);
}
}
try {
await wrapAsync('fetch user data', async () => {
throw new Error('Network timeout');
});
} catch (e) {
console.log(e.message); // "Failed to fetch user data"
console.log(e.operation); // "fetch user data"
console.log(e.originalError.message); // "Network timeout"
}
// Retryable error
class RetryableError extends Error {
constructor(message, retryAfter = 1000) {
super(message);
this.name = 'RetryableError';
this.retryAfter = retryAfter;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
async function fetchWithRetry(url, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fetch(url);
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
throw new RetryableError(
`Attempt ${attempt} failed`,
1000 * attempt
);
}
}
}
Best Practices: Extend
Error class for custom errors. Set name
property to class name. Use Error.captureStackTrace for clean stack traces. Add domain-specific
properties (field, statusCode, code). Create error hierarchies for different
error categories. Implement toJSON() for serialization.
3. Exception Propagation and Error Boundaries
Exception Propagation Rules
| Context | Behavior | Handling |
|---|---|---|
| Synchronous code | Propagates up call stack | try-catch in caller |
| Promise chain | Propagates through .then() chain | .catch() or try-catch with await |
| Async function | Returns rejected promise | try-catch or .catch() |
| Event handlers | Does not propagate to caller | Handle in event handler or window.onerror |
| Callbacks | Does not propagate to caller | Error-first callback pattern |
| setTimeout/setInterval | Does not propagate | Handle in callback or window.onerror |
Error Boundary Patterns
| Pattern | Scope | Use Case |
|---|---|---|
| try-catch | Block-level | Synchronous code, async/await |
| .catch() | Promise chain | Promise-based operations |
| finally | Cleanup after try-catch | Resource cleanup (always runs) |
| window.onerror | Global (window) | Uncaught exceptions |
| window.onunhandledrejection | Global (promises) | Unhandled promise rejections |
| process.on('uncaughtException') | Global (Node.js) | Uncaught exceptions in Node |
| process.on('unhandledRejection') | Global (Node.js promises) | Unhandled rejections in Node |
Error Recovery Strategies
| Strategy | Description | When to Use |
|---|---|---|
| Fail fast | Throw immediately, don't catch | Programming errors, invalid state |
| Retry | Attempt operation again | Transient failures (network, rate limits) |
| Fallback | Use alternative approach/data | Non-critical features, degraded mode |
| Circuit breaker | Stop trying after repeated failures | External service failures |
| Log and continue | Record error but don't stop | Non-critical operations |
| Graceful degradation | Reduce functionality but keep working | Feature failures in production |
Example: Exception propagation patterns
// Synchronous propagation
function level3() {
throw new Error('Error at level 3');
}
function level2() {
level3(); // Error propagates up
}
function level1() {
try {
level2();
} catch (e) {
console.log('Caught at level 1:', e.message);
}
}
level1(); // "Caught at level 1: Error at level 3"
// Async propagation with promises
function asyncLevel3() {
return Promise.reject(new Error('Async error'));
}
function asyncLevel2() {
return asyncLevel3(); // Rejection propagates
}
function asyncLevel1() {
return asyncLevel2()
.catch(e => {
console.log('Caught in promise chain:', e.message);
});
}
asyncLevel1();
// Async/await propagation
async function asyncAwaitLevel3() {
throw new Error('Async/await error');
}
async function asyncAwaitLevel2() {
await asyncAwaitLevel3(); // Error propagates
}
async function asyncAwaitLevel1() {
try {
await asyncAwaitLevel2();
} catch (e) {
console.log('Caught with async/await:', e.message);
}
}
asyncAwaitLevel1();
// Event handler - no propagation
document.getElementById('button').addEventListener('click', () => {
throw new Error('Error in event handler');
// This will NOT be caught by outer try-catch
});
// Must handle in event handler
document.getElementById('button').addEventListener('click', () => {
try {
// risky operation
throw new Error('Handled error');
} catch (e) {
console.log('Caught in handler:', e.message);
}
});
// Callback - no propagation
function asyncOperation(callback) {
setTimeout(() => {
try {
throw new Error('Error in callback');
} catch (e) {
callback(e, null); // Error-first callback pattern
}
}, 100);
}
asyncOperation((error, result) => {
if (error) {
console.log('Error received:', error.message);
return;
}
console.log('Result:', result);
});
Example: Global error handlers
// Browser: Global error handler
window.onerror = function(message, source, lineno, colno, error) {
console.log('Global error caught:');
console.log('Message:', message);
console.log('Source:', source);
console.log('Line:', lineno, 'Column:', colno);
console.log('Error object:', error);
// Return true to prevent default error handling
return true;
};
// Browser: Unhandled promise rejections
window.onunhandledrejection = function(event) {
console.log('Unhandled rejection:');
console.log('Reason:', event.reason);
console.log('Promise:', event.promise);
// Prevent default handling
event.preventDefault();
};
// Alternative with addEventListener
window.addEventListener('error', (event) => {
console.log('Error event:', event.error);
});
window.addEventListener('unhandledrejection', (event) => {
console.log('Unhandled rejection:', event.reason);
});
// Node.js: Global error handlers
process.on('uncaughtException', (error, origin) => {
console.error('Uncaught exception:');
console.error('Error:', error);
console.error('Origin:', origin);
// Clean up and exit
// process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection:');
console.error('Reason:', reason);
console.error('Promise:', promise);
});
// Example: Triggering global handlers
setTimeout(() => {
throw new Error('Uncaught error in setTimeout');
}, 100);
Promise.reject(new Error('Unhandled promise rejection'));
// Centralized error logger
class ErrorLogger {
static log(error, context = {}) {
const errorInfo = {
message: error.message,
name: error.name,
stack: error.stack,
timestamp: new Date().toISOString(),
context,
url: typeof window !== 'undefined' ? window.location.href : null,
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : null
};
console.error('Error logged:', errorInfo);
// Send to monitoring service
// sendToMonitoring(errorInfo);
}
}
window.onerror = function(message, source, lineno, colno, error) {
ErrorLogger.log(error || new Error(message), {
source,
lineno,
colno
});
return true;
};
window.onunhandledrejection = function(event) {
ErrorLogger.log(
event.reason instanceof Error
? event.reason
: new Error(String(event.reason)),
{type: 'unhandled-rejection'}
);
};
Example: Error recovery strategies
// Retry with exponential backoff
async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
const delay = baseDelay * Math.pow(2, attempt - 1);
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Usage
await retryWithBackoff(async () => {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('Fetch failed');
return response.json();
});
// Fallback pattern
async function fetchWithFallback(primaryUrl, fallbackUrl) {
try {
return await fetch(primaryUrl);
} catch (error) {
console.log('Primary failed, using fallback');
return await fetch(fallbackUrl);
}
}
// Circuit breaker
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
}
async execute(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
console.log(`Circuit breaker OPEN for ${this.timeout}ms`);
}
}
}
const breaker = new CircuitBreaker(3, 30000);
async function callExternalService() {
return await breaker.execute(async () => {
const response = await fetch('/external-api');
if (!response.ok) throw new Error('Service unavailable');
return response.json();
});
}
// Graceful degradation
async function getUserData(userId) {
try {
// Try to fetch from API
const response = await fetch(`/api/users/${userId}`);
return await response.json();
} catch (error) {
console.warn('API unavailable, using cached data');
try {
// Fallback to cache
return await getCachedUserData(userId);
} catch (cacheError) {
console.warn('Cache unavailable, using default data');
// Final fallback to defaults
return {
id: userId,
name: 'Guest User',
avatar: '/default-avatar.png'
};
}
}
}
Key Points: Exceptions propagate up synchronous call stack until
caught. Promises propagate rejections through chain. Event handlers and timers don't propagate errors. Use
window.onerror and onunhandledrejection for global handling. Implement retry with
exponential backoff for transient failures. Circuit breaker pattern prevents cascading failures.
4. Console API (console.log, console.error, console.table, console.trace)
Console Logging Methods
| Method | Purpose | Output Style |
|---|---|---|
| console.log() | General logging | Standard output |
| console.info() | Informational messages | Info icon (browser dependent) |
| console.warn() | Warning messages | Yellow warning icon |
| console.error() | Error messages | Red error icon with stack trace |
| console.debug() | Debug messages (may be hidden) | Debug level (filterable) |
Console Formatting and Inspection
| Method | Syntax | Description |
|---|---|---|
| console.dir() | console.dir(object) |
Display object properties interactively |
| console.dirxml() | console.dirxml(node) |
Display XML/HTML element tree |
| console.table() | console.table(array) |
Display array/object as table |
| console.group() | console.group(label) |
Start collapsible group |
| console.groupCollapsed() | console.groupCollapsed(label) |
Start collapsed group |
| console.groupEnd() | console.groupEnd() |
End current group |
Console Utilities
| Method | Syntax | Description |
|---|---|---|
| console.assert() | console.assert(condition, message) |
Log error if condition false |
| console.count() | console.count(label) |
Count calls with label |
| console.countReset() | console.countReset(label) |
Reset counter for label |
| console.time() | console.time(label) |
Start timer with label |
| console.timeLog() | console.timeLog(label) |
Log current timer value |
| console.timeEnd() | console.timeEnd(label) |
Stop timer and log duration |
| console.trace() | console.trace(message) |
Log stack trace |
| console.clear() | console.clear() |
Clear console |
String Substitution Patterns
| Pattern | Type | Example |
|---|---|---|
| %s | String | console.log('Hello %s', 'World') |
| %d or %i | Integer | console.log('Count: %d', 42) |
| %f | Float | console.log('Pi: %f', 3.14159) |
| %o | Object | console.log('Object: %o', obj) |
| %O | Object (optimized) | console.log('Object: %O', obj) |
| %c | CSS styling | console.log('%cStyled', 'color: red') |
Example: Basic console methods
// Log levels
console.log('Standard log message');
console.info('Informational message');
console.warn('Warning message');
console.error('Error message');
console.debug('Debug message');
// Multiple arguments
console.log('Name:', 'John', 'Age:', 30, 'Active:', true);
// String substitution
console.log('Hello %s, you are %d years old', 'Alice', 25);
console.log('Pi is approximately %f', Math.PI);
// CSS styling
console.log(
'%cStyled Text',
'color: blue; font-size: 20px; font-weight: bold'
);
console.log(
'%cError: %cSomething went wrong',
'color: red; font-weight: bold',
'color: orange'
);
// Object inspection
const user = {
name: 'John',
age: 30,
address: {
city: 'New York',
country: 'USA'
}
};
console.log(user); // Standard object display
console.dir(user); // Interactive object properties
console.dir(user, {depth: null}); // Show all nested levels
// Table display
const users = [
{id: 1, name: 'Alice', age: 25},
{id: 2, name: 'Bob', age: 30},
{id: 3, name: 'Charlie', age: 35}
];
console.table(users);
console.table(users, ['name', 'age']); // Show specific columns
// DOM element
const element = document.getElementById('app');
console.log(element); // DOM representation
console.dir(element); // Object properties
console.dirxml(element); // XML tree structure
Example: Console grouping and organization
// Simple grouping
console.group('User Details');
console.log('Name: John');
console.log('Age: 30');
console.log('Email: john@example.com');
console.groupEnd();
// Nested groups
console.group('Application Startup');
console.log('Initializing...');
console.group('Loading Modules');
console.log('Module A loaded');
console.log('Module B loaded');
console.log('Module C loaded');
console.groupEnd();
console.group('Connecting to Services');
console.log('Database connected');
console.log('API connected');
console.groupEnd();
console.log('Startup complete');
console.groupEnd();
// Collapsed groups
console.groupCollapsed('Debug Information');
console.log('This is collapsed by default');
console.log('User must expand to see');
console.groupEnd();
// Organizing API calls
async function fetchUserData(userId) {
console.group(`Fetching user ${userId}`);
console.time('fetch-duration');
try {
console.log('Sending request...');
const response = await fetch(`/api/users/${userId}`);
console.log('Response:', response.status);
const data = await response.json();
console.log('Data received:', data);
console.timeEnd('fetch-duration');
console.groupEnd();
return data;
} catch (error) {
console.error('Fetch failed:', error);
console.timeEnd('fetch-duration');
console.groupEnd();
throw error;
}
}
// Function call tracing
function traceFunction(fn, name) {
return function(...args) {
console.group(`${name} called`);
console.log('Arguments:', args);
try {
const result = fn(...args);
console.log('Result:', result);
console.groupEnd();
return result;
} catch (error) {
console.error('Error:', error);
console.groupEnd();
throw error;
}
};
}
const add = traceFunction((a, b) => a + b, 'add');
add(2, 3); // Logs group with args and result
Example: Console utilities
// Assertions
console.assert(1 === 1, 'This will not log');
console.assert(1 === 2, 'This WILL log as error');
function divide(a, b) {
console.assert(b !== 0, 'Division by zero');
return a / b;
}
divide(10, 0); // Logs assertion error
// Counting
function processItem(item) {
console.count('processItem');
console.log('Processing:', item);
}
processItem('A'); // "processItem: 1"
processItem('B'); // "processItem: 2"
processItem('C'); // "processItem: 3"
console.countReset('processItem');
processItem('D'); // "processItem: 1" (reset)
// Counting with labels
function handleEvent(type) {
console.count(type);
}
handleEvent('click'); // "click: 1"
handleEvent('scroll'); // "scroll: 1"
handleEvent('click'); // "click: 2"
handleEvent('scroll'); // "scroll: 2"
// Timing operations
console.time('operation');
// Simulate work
for (let i = 0; i < 1000000; i++) {
// work
}
console.timeLog('operation'); // Intermediate timing
// More work
console.timeEnd('operation'); // Final timing and stop
// Timing async operations
async function measureAsync() {
console.time('async-operation');
await fetch('/api/data');
console.timeLog('async-operation', 'After fetch');
await processData();
console.timeLog('async-operation', 'After processing');
console.timeEnd('async-operation');
}
// Multiple timers
console.time('timer-1');
console.time('timer-2');
setTimeout(() => console.timeEnd('timer-1'), 100);
setTimeout(() => console.timeEnd('timer-2'), 200);
// Stack trace
function a() {
b();
}
function b() {
c();
}
function c() {
console.trace('Called from c()');
}
a(); // Shows full call stack: c <- b <- a
// Performance monitoring
function measurePerformance(fn, name) {
console.time(name);
console.count(name);
try {
const result = fn();
console.timeEnd(name);
return result;
} catch (error) {
console.error(`${name} failed:`, error);
console.timeEnd(name);
throw error;
}
}
measurePerformance(() => {
// Some operation
return Array(1000).fill(0).map((_, i) => i * 2);
}, 'array-operation');
Example: Custom logger implementation
// Production-ready logger
class Logger {
constructor(options = {}) {
this.level = options.level || 'info';
this.prefix = options.prefix || '';
this.levels = {
debug: 0,
info: 1,
warn: 2,
error: 3
};
}
shouldLog(level) {
return this.levels[level] >= this.levels[this.level];
}
format(level, ...args) {
const timestamp = new Date().toISOString();
const prefix = this.prefix ? `[${this.prefix}]` : '';
return [`[${timestamp}]`, prefix, `[${level.toUpperCase()}]`, ...args];
}
debug(...args) {
if (this.shouldLog('debug')) {
console.debug(...this.format('debug', ...args));
}
}
info(...args) {
if (this.shouldLog('info')) {
console.info(...this.format('info', ...args));
}
}
warn(...args) {
if (this.shouldLog('warn')) {
console.warn(...this.format('warn', ...args));
}
}
error(...args) {
if (this.shouldLog('error')) {
console.error(...this.format('error', ...args));
console.trace();
}
}
group(label) {
console.group(...this.format('group', label));
}
groupEnd() {
console.groupEnd();
}
time(label) {
console.time(label);
}
timeEnd(label) {
console.timeEnd(label);
}
}
// Usage
const logger = new Logger({
level: 'info',
prefix: 'MyApp'
});
logger.debug('This will not show (below info level)');
logger.info('Application started');
logger.warn('Low memory');
logger.error('Fatal error occurred');
// Module-specific loggers
const apiLogger = new Logger({level: 'debug', prefix: 'API'});
const dbLogger = new Logger({level: 'info', prefix: 'DB'});
apiLogger.debug('Request received');
dbLogger.info('Query executed');
// Environment-aware logger
const isDevelopment = process.env.NODE_ENV === 'development';
const appLogger = new Logger({
level: isDevelopment ? 'debug' : 'warn',
prefix: 'App'
});
appLogger.debug('This shows only in development');
Key Points: Use
console.log() for general, console.warn() for
warnings,
console.error() for errors. console.table() displays arrays beautifully. Group related
logs with console.group(). console.time() measures performance. Use
console.assert() for condition checks. %c enables CSS styling.
Implement custom logger for production with log levels.
5. Debugging Techniques and Breakpoint Usage
Breakpoint Types
| Type | Usage | When to Use |
|---|---|---|
| Line breakpoint | Click line number in debugger | Pause at specific line |
| Conditional breakpoint | Right-click line, add condition | Pause only when condition true |
| Logpoint | Log without stopping execution | Non-intrusive logging |
| debugger statement | debugger; in code |
Programmatic breakpoint |
| DOM breakpoint | Break on attribute/subtree changes | Debug DOM mutations |
| XHR breakpoint | Break on network requests | Debug AJAX calls |
| Event listener breakpoint | Break on event types | Debug event handling |
| Exception breakpoint | Break on caught/uncaught exceptions | Debug error handling |
Debugger Commands
| Command | Shortcut | Action |
|---|---|---|
| Continue | F8 / Cmd+\ | Resume execution until next breakpoint |
| Step Over | F10 / Cmd+' | Execute current line, don't enter functions |
| Step Into | F11 / Cmd+; | Enter function calls |
| Step Out | Shift+F11 / Shift+Cmd+; | Exit current function |
| Step | F9 | Single-step through code (Node.js) |
Debug Console Commands
| Command | Description | Example |
|---|---|---|
| Evaluate expression | Type any JavaScript expression | user.name, 2 + 2 |
| Modify variables | Change values while paused | count = 10 |
| Call functions | Execute functions | processData() |
| $0 - $4 | Recently inspected elements | $0.classList.add('active') |
| $_ | Last evaluated expression | $_ * 2 |
| $() | querySelector shorthand | $('#myId') |
| $$() | querySelectorAll shorthand | $$('.myClass') |
Debugging Best Practices
| Practice | Description | Benefit |
|---|---|---|
| Source maps | Map minified code to original | Debug production builds |
| Conditional breakpoints | Break only on specific conditions | Avoid breaking on every iteration |
| Watch expressions | Monitor variable values | Track state changes |
| Call stack inspection | View function call hierarchy | Understand execution flow |
| Blackbox scripts | Skip library code | Focus on application code |
Example: Debugger statement and breakpoints
// debugger statement
function calculateTotal(items) {
let total = 0;
for (const item of items) {
debugger; // Execution pauses here when DevTools open
total += item.price * item.quantity;
}
return total;
}
// Conditional debugger
function processUsers(users) {
for (const user of users) {
// Only break for specific user
if (user.id === 123) {
debugger;
}
processUser(user);
}
}
// Debug with assertions
function divide(a, b) {
console.assert(typeof a === 'number', 'a must be number');
console.assert(typeof b === 'number', 'b must be number');
console.assert(b !== 0, 'Division by zero');
return a / b;
}
// Debug wrapper
function debug(fn, name) {
return function(...args) {
console.group(`Debug: ${name}`);
console.log('Arguments:', args);
console.time(name);
debugger; // Pause before execution
try {
const result = fn(...args);
console.log('Result:', result);
console.timeEnd(name);
console.groupEnd();
return result;
} catch (error) {
console.error('Error:', error);
console.timeEnd(name);
console.groupEnd();
throw error;
}
};
}
const debugAdd = debug((a, b) => a + b, 'add');
debugAdd(2, 3);
// Breakpoint on error
function riskyOperation() {
try {
// risky code
throw new Error('Something went wrong');
} catch (error) {
debugger; // Pause to inspect error
console.error('Error caught:', error);
}
}
Example: Debugging async code
// Debug promise chain
fetch('/api/users')
.then(response => {
debugger; // Inspect response
return response.json();
})
.then(data => {
debugger; // Inspect parsed data
console.log('Users:', data);
})
.catch(error => {
debugger; // Inspect error
console.error('Fetch failed:', error);
});
// Debug async/await
async function fetchUserData(userId) {
try {
console.log('Fetching user:', userId);
debugger; // Before fetch
const response = await fetch(`/api/users/${userId}`);
debugger; // After fetch, inspect response
const data = await response.json();
debugger; // After parsing, inspect data
return data;
} catch (error) {
debugger; // On error
console.error('Error:', error);
throw error;
}
}
// Debug with detailed logging
async function debugAsyncOperation(operation, name) {
console.group(`Async: ${name}`);
console.time(name);
try {
console.log('Starting...');
const result = await operation();
console.log('Completed:', result);
console.timeEnd(name);
console.groupEnd();
return result;
} catch (error) {
console.error('Failed:', error);
console.trace();
console.timeEnd(name);
console.groupEnd();
throw error;
}
}
// Usage
await debugAsyncOperation(
() => fetch('/api/data').then(r => r.json()),
'fetch-data'
);
// Debug race conditions
let requestCount = 0;
async function debugRaceCondition() {
const id = ++requestCount;
console.log(`Request ${id} started`);
debugger;
await new Promise(r => setTimeout(r, Math.random() * 1000));
console.log(`Request ${id} completed`);
debugger;
}
// Multiple concurrent calls
Promise.all([
debugRaceCondition(),
debugRaceCondition(),
debugRaceCondition()
]);
Example: Advanced debugging techniques
// Performance profiling
function profileFunction(fn, iterations = 1000) {
console.time('profile');
for (let i = 0; i < iterations; i++) {
fn();
}
console.timeEnd('profile');
}
profileFunction(() => {
// Code to profile
const arr = Array(100).fill(0).map((_, i) => i * 2);
});
// Memory usage tracking (Chrome)
function checkMemory() {
if (performance.memory) {
console.group('Memory Usage');
console.log('Used:',
Math.round(performance.memory.usedJSHeapSize / 1048576), 'MB');
console.log('Total:',
Math.round(performance.memory.totalJSHeapSize / 1048576), 'MB');
console.log('Limit:',
Math.round(performance.memory.jsHeapSizeLimit / 1048576), 'MB');
console.groupEnd();
}
}
// Before operation
checkMemory();
// Do memory-intensive work
const largeArray = Array(1000000).fill({data: 'test'});
// After operation
checkMemory();
// Stack trace analysis
function captureCallers() {
const stack = new Error().stack;
const lines = stack.split('\n');
console.group('Call Stack');
lines.slice(2).forEach(line => { // Skip Error and this function
console.log(line.trim());
});
console.groupEnd();
}
function a() { b(); }
function b() { c(); }
function c() { captureCallers(); }
a();
// Debugging higher-order functions
function debugHOF(fn, name) {
return function(...args) {
console.group(`HOF: ${name}`);
console.log('Input:', args);
const result = fn(...args);
console.log('Output:', result);
console.groupEnd();
return result;
};
}
const numbers = [1, 2, 3, 4, 5];
const debugMap = debugHOF(x => x * 2, 'double');
const debugFilter = debugHOF(x => x > 2, 'greaterThan2');
const debugReduce = debugHOF((a, b) => a + b, 'sum');
const doubled = numbers.map(debugMap);
const filtered = numbers.filter(debugFilter);
const sum = numbers.reduce(debugReduce, 0);
// Debug event listeners
function debugEventListener(element, event, handler) {
element.addEventListener(event, function(...args) {
console.group(`Event: ${event}`);
console.log('Target:', args[0].target);
console.log('Event:', args[0]);
console.trace('Call stack');
const result = handler.apply(this, args);
console.groupEnd();
return result;
});
}
const button = document.getElementById('myButton');
debugEventListener(button, 'click', (e) => {
console.log('Button clicked');
});
Key Points: Use
debugger statement for programmatic breakpoints. Conditional
breakpoints prevent breaking on every iteration. Step Over skips function calls,
Step Into enters them. Inspect call stack to understand execution flow. Use
watch expressions to monitor variables. Enable source maps for debugging transpiled code.
6. Error Monitoring and Production Debugging
Error Monitoring Services
| Service | Features | Use Case |
|---|---|---|
| Sentry | Error tracking, performance, releases | Full-featured error monitoring |
| Rollbar | Real-time error tracking | Error aggregation and alerts |
| Bugsnag | Error monitoring, stability score | Mobile and web apps |
| LogRocket | Session replay, error tracking | User session debugging |
| Datadog | APM, logging, error tracking | Full observability platform |
| New Relic | APM, browser monitoring | Application performance |
Error Context Data
| Category | Data | Purpose |
|---|---|---|
| User Info | ID, email, username | Identify affected users |
| Environment | Browser, OS, device | Reproduce environment |
| Application | Version, build, release | Track regressions |
| Request | URL, method, headers | Understand context |
| Breadcrumbs | User actions, navigation | Trace user journey |
| Custom Tags | Feature flags, experiments | Segment and filter errors |
Production Debugging Strategies
| Strategy | Implementation | Benefit |
|---|---|---|
| Source maps | Upload to error monitoring service | Debug minified code |
| Structured logging | JSON format with context | Searchable, parseable logs |
| Correlation IDs | Track requests across services | Distributed tracing |
| Feature flags | Enable debug mode for specific users | Production debugging |
| Error sampling | Rate limit error reports | Reduce noise, cost |
| Release tracking | Tag errors with version | Identify regressions |
Example: Error monitoring setup
// Sentry integration example
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: 'YOUR_SENTRY_DSN',
environment: process.env.NODE_ENV,
release: 'my-app@1.0.0',
// Performance monitoring
tracesSampleRate: 0.1,
// Error filtering
beforeSend(event, hint) {
// Filter out certain errors
if (event.exception) {
const error = hint.originalException;
// Ignore network errors
if (error.message.includes('NetworkError')) {
return null;
}
// Ignore specific status codes
if (error.statusCode === 404) {
return null;
}
}
return event;
},
// PII scrubbing
beforeBreadcrumb(breadcrumb) {
if (breadcrumb.category === 'xhr') {
// Remove sensitive data from URLs
breadcrumb.data.url = breadcrumb.data.url.replace(
/token=([^&]+)/,
'token=[REDACTED]'
);
}
return breadcrumb;
}
});
// Set user context
Sentry.setUser({
id: user.id,
email: user.email,
username: user.username
});
// Set custom tags
Sentry.setTag('feature_flag_new_ui', 'enabled');
Sentry.setTag('subscription_tier', 'premium');
// Set custom context
Sentry.setContext('app', {
build: '12345',
commit: 'abc123'
});
// Manual error reporting
try {
riskyOperation();
} catch (error) {
Sentry.captureException(error, {
level: 'error',
tags: {
operation: 'riskyOperation'
},
contexts: {
operation: {
input: operationInput,
attemptNumber: retryCount
}
}
});
}
// Breadcrumbs for debugging
Sentry.addBreadcrumb({
category: 'auth',
message: 'User logged in',
level: 'info'
});
Sentry.addBreadcrumb({
category: 'navigation',
message: 'Navigated to /dashboard',
level: 'info'
});
// Capture message (non-error)
Sentry.captureMessage('Unusual condition detected', 'warning');
Example: Production logging system
// Structured logger for production
class ProductionLogger {
constructor(config = {}) {
this.serviceName = config.serviceName || 'app';
this.environment = config.environment || 'production';
this.version = config.version || '1.0.0';
}
createLogEntry(level, message, context = {}) {
return {
timestamp: new Date().toISOString(),
level,
message,
service: this.serviceName,
environment: this.environment,
version: this.version,
correlation_id: context.correlationId || this.generateCorrelationId(),
user_id: context.userId,
session_id: context.sessionId,
url: typeof window !== 'undefined' ? window.location.href : undefined,
user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
...context.extra
};
}
generateCorrelationId() {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
log(level, message, context) {
const entry = this.createLogEntry(level, message, context);
// Send to logging service
this.send(entry);
// Also log to console in development
if (this.environment === 'development') {
console[level](message, entry);
}
}
async send(entry) {
try {
await fetch('/api/logs', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(entry)
});
} catch (error) {
// Fallback: log to console
console.error('Failed to send log:', error);
}
}
info(message, context) {
this.log('info', message, context);
}
warn(message, context) {
this.log('warn', message, context);
}
error(message, error, context = {}) {
this.log('error', message, {
...context,
extra: {
...context.extra,
error_name: error.name,
error_message: error.message,
error_stack: error.stack
}
});
}
}
const logger = new ProductionLogger({
serviceName: 'frontend-app',
environment: process.env.NODE_ENV,
version: process.env.APP_VERSION
});
// Usage
logger.info('User logged in', {
userId: '123',
extra: {
login_method: 'oauth'
}
});
try {
await fetchData();
} catch (error) {
logger.error('Failed to fetch data', error, {
userId: currentUser.id,
extra: {
endpoint: '/api/data',
retry_count: 3
}
});
}
Example: Error sampling and rate limiting
// Error sampling to reduce noise
class ErrorSampler {
constructor(sampleRate = 0.1) {
this.sampleRate = sampleRate;
this.errorCounts = new Map();
this.resetInterval = 60000; // 1 minute
setInterval(() => this.errorCounts.clear(), this.resetInterval);
}
shouldReport(error) {
const errorKey = `${error.name}:${error.message}`;
const count = this.errorCounts.get(errorKey) || 0;
// Always report first occurrence
if (count === 0) {
this.errorCounts.set(errorKey, 1);
return true;
}
// Sample subsequent occurrences
this.errorCounts.set(errorKey, count + 1);
return Math.random() < this.sampleRate;
}
report(error, context = {}) {
if (this.shouldReport(error)) {
// Send to monitoring service
errorMonitoring.captureException(error, {
...context,
extra: {
...context.extra,
error_count: this.errorCounts.get(
`${error.name}:${error.message}`
)
}
});
}
}
}
const sampler = new ErrorSampler(0.1); // 10% sample rate
// Use in error handler
window.onerror = function(message, source, lineno, colno, error) {
sampler.report(error || new Error(message), {
extra: {source, lineno, colno}
});
};
// Rate limiter for specific error types
class ErrorRateLimiter {
constructor(maxErrors = 10, windowMs = 60000) {
this.maxErrors = maxErrors;
this.windowMs = windowMs;
this.errors = new Map();
}
canReport(errorType) {
const now = Date.now();
const errors = this.errors.get(errorType) || [];
// Remove old errors outside window
const recentErrors = errors.filter(
time => now - time < this.windowMs
);
if (recentErrors.length >= this.maxErrors) {
return false;
}
recentErrors.push(now);
this.errors.set(errorType, recentErrors);
return true;
}
}
const rateLimiter = new ErrorRateLimiter(10, 60000);
function reportError(error) {
if (rateLimiter.canReport(error.name)) {
errorMonitoring.captureException(error);
} else {
console.warn(`Rate limit exceeded for ${error.name}`);
}
}
Example: Release tracking and version management
// Release tracking
class ReleaseTracker {
constructor(config) {
this.version = config.version;
this.buildId = config.buildId;
this.gitCommit = config.gitCommit;
this.environment = config.environment;
this.initMonitoring();
}
initMonitoring() {
// Configure error monitoring with release info
errorMonitoring.setRelease({
version: this.version,
buildId: this.buildId,
commit: this.gitCommit
});
// Track release deployment
this.trackDeployment();
}
async trackDeployment() {
try {
await fetch('/api/deployments', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
version: this.version,
buildId: this.buildId,
commit: this.gitCommit,
environment: this.environment,
timestamp: new Date().toISOString()
})
});
} catch (error) {
console.error('Failed to track deployment:', error);
}
}
async compareWithPrevious() {
// Fetch error rates from monitoring service
const currentErrors = await this.getErrorRate(this.version);
const previousErrors = await this.getErrorRate(this.previousVersion);
const increase = currentErrors - previousErrors;
const percentIncrease = (increase / previousErrors) * 100;
if (percentIncrease > 50) {
this.alertHighErrorRate(percentIncrease);
}
}
}
// Initialize on app start
const tracker = new ReleaseTracker({
version: process.env.APP_VERSION,
buildId: process.env.BUILD_ID,
gitCommit: process.env.GIT_COMMIT,
environment: process.env.NODE_ENV
});
// Feature flag debugging
class FeatureFlagDebugger {
constructor() {
this.flags = new Map();
this.debugEnabled = false;
}
enableDebug(userId) {
// Enable verbose logging for specific user
if (this.isDebugUser(userId)) {
this.debugEnabled = true;
console.log('Debug mode enabled for user:', userId);
}
}
isDebugUser(userId) {
// Check if user is in debug list
return ['debug-user-1', 'admin'].includes(userId);
}
log(...args) {
if (this.debugEnabled) {
console.log('[DEBUG]', ...args);
}
}
setFlag(name, value) {
this.flags.set(name, value);
this.log(`Feature flag set: ${name} = ${value}`);
}
getFlag(name) {
const value = this.flags.get(name);
this.log(`Feature flag get: ${name} = ${value}`);
return value;
}
}
const featureFlags = new FeatureFlagDebugger();
// Enable debug mode for specific user
if (currentUser.id === 'debug-user-1') {
featureFlags.enableDebug(currentUser.id);
}
Best Practices: Use error monitoring service like Sentry or
Rollbar.
Include context: user, environment, version. Implement error sampling to reduce
noise. Upload source maps for debugging minified code. Track releases to identify regressions. Use structured
logging with correlation IDs. Implement rate limiting for error reporting. Enable debug mode for specific users
in production.
Section 19 Summary: Error Handling and Debugging Techniques
- Error Types: Error, TypeError, ReferenceError, RangeError, SyntaxError, URIError, AggregateError
- Error Properties: name, message, stack, cause (ES2022)
- Custom Errors: Extend Error class, add domain-specific properties
- Exception Propagation: Sync code up call stack, promises through chain, async/await
- Global Handlers: window.onerror, onunhandledrejection, process uncaughtException
- Error Recovery: Retry with backoff, fallback, circuit breaker, graceful degradation
- Console API: log, warn, error, table, group, time, trace, assert
- Debugging: debugger statement, breakpoints, step commands, watch expressions
- Production Monitoring: Sentry, Rollbar, structured logging, source maps
- Best Practices: Error sampling, rate limiting, release tracking, correlation IDs