Technology · JavaScript
JavaScript Error Handling
Handle errors gracefully in JavaScript using try/catch, custom error classes, and finally blocks.
TL;DR
- 01Wrap risky code in try/catch to handle thrown errors cleanly.
- 02Throw custom error classes to make catch blocks more precise.
- 03Use finally to run cleanup code regardless of success or failure.
Try/Catch Basics
- Wrap risky code in try block to intercept runtime errors.
try { const result = riskyOperation(); console.log(result); } catch (error) { console.error('Error:', error.message); } - The catch block only runs when an error is thrown in try.
try { const data = JSON.parse('invalid'); } catch (error) { console.error('Caught:', error.message); // SyntaxError } - The error object contains a message and a stack trace.
catch (error) { console.log(error.name); // "SyntaxError" console.log(error.message); // "Unexpected token i" console.log(error.stack); // full trace } - Code inside try after the thrown line does not execute.
try { throw new Error('stop here'); console.log('never runs'); } catch (e) { console.log(e.message); // "stop here" } - Omit the catch binding if you don't need the error object.
try { mayFail(); } catch { // optional binding — no variable needed console.log('Something went wrong'); }
Finally Block
- Run cleanup code with finally that always executes.
try { const file = openFile('data.txt'); processFile(file); } catch (error) { console.error('Error:', error); } finally { closeFile(); // Always runs } - Finally runs even when there is no error in try.
- Finally runs even if catch re-throws or returns early.
function getData() { try { return fetchData(); } finally { cleanup(); // runs before function returns } } - Use finally to release resources like connections or file handles.
let connection; try { connection = openDB(); return connection.query('SELECT * FROM users'); } finally { connection?.close(); } - Finally is useful for resetting loading or spinner state in UIs.
setLoading(true); try { await fetchData(); } finally { setLoading(false); // runs on success or failure }
Throwing Errors
- Throw a new Error with a descriptive message.
function divide(a, b) { if (b === 0) { throw new Error('Division by zero'); } return a / b; } - Throw any value, but Error objects are best practice.
// Avoid: throw 'something went wrong'; // Prefer: throw new Error('something went wrong'); - Throw built-in error types for more specific problems.
function setAge(age) { if (typeof age !== 'number') { throw new TypeError('Age must be a number'); } if (age < 0 || age > 150) { throw new RangeError('Age out of valid range'); } } - Re-throw errors after logging to let upstream code handle them.
try { riskyOp(); } catch (e) { logger.error(e); throw e; // propagate to caller } - Throwing inside a catch block escalates the error upstream.
catch (error) { if (error instanceof SyntaxError) { throw new Error('Config file is malformed'); } }
Custom Error Classes
- Create custom error types by extending the Error class.
class ValidationError extends Error { constructor(message) { super(message); this.name = 'ValidationError'; } } - Check error type with instanceof in catch blocks.
try { if (!email.includes('@')) { throw new ValidationError('Invalid email'); } } catch (error) { if (error instanceof ValidationError) { console.log('Validation error:', error.message); } } - Add extra properties to custom errors for richer context.
class HttpError extends Error { constructor(status, message) { super(message); this.name = 'HttpError'; this.status = status; } } throw new HttpError(404, 'Resource not found'); - Use multiple custom error classes to categorize problems.
class NetworkError extends Error { } class AuthError extends Error { } class NotFoundError extends Error { } - Handle specific error types separately in catch.
catch (error) { if (error instanceof AuthError) return redirectToLogin(); if (error instanceof NetworkError) return showRetry(); throw error; // unknown errors bubble up }
Common Error Types
- SyntaxError occurs when code or data cannot be parsed.
try { JSON.parse('invalid json'); } catch (error) { if (error instanceof SyntaxError) { console.log('Invalid JSON format'); } } - TypeError occurs when a value is used with the wrong type.
try { const x = null; x.method(); // TypeError: Cannot read properties of null } catch (e) { console.log(e instanceof TypeError); // true } - ReferenceError occurs when a variable is not defined.
try { console.log(undeclaredVar); } catch (e) { console.log(e instanceof ReferenceError); // true } - RangeError occurs when a number falls outside valid bounds.
try { new Array(-1); // RangeError: Invalid array length } catch (e) { console.log(e instanceof RangeError); // true } - Check error names as a string alternative to instanceof.
catch (error) { console.log(error.name); // "TypeError", "RangeError", etc. if (error.name === 'TypeError') handleTypeError(error); }
Tip: Create custom error classes to identify error types in catch blocks precisely — makes branching logic far clearer than checking messages.
Warning: Never swallow errors silently with an empty catch block — always log or handle them so bugs do not disappear unnoticed.