Error Handling
An error is thrown with throw and handled with
try...catch...finally. A thrown error interrupts the normal flow and goes
up the call stack to the nearest catch. If there is none, it becomes an
uncaught error. finally runs in any case.
Errors are objects of the Error class or its subclasses: built-in
(TypeError, ReferenceError, SyntaxError, RangeError) or custom ones
created by extending Error.
Details
- An error object has
name,message,stackand an optionalcause. throwaccepts any value, but always throwErrorobjects: only they have a stack trace.try...catchis synchronous. It does not catch an error thrown later inside a callback, for example insetTimeout.- In asynchronous code a rejected promise is handled with
.catch()or withtry...catcharoundawait. See Promises and async/await. - Unhandled errors can be observed globally: the
errorandunhandledrejectionevents ofwindow. - Catch only what you can handle. Rethrow the rest, so errors are not swallowed.
Example
class ValidationError extends Error {
constructor(message, options) {
super(message, options);
this.name = 'ValidationError';
}
}
function getAccess(age) {
if (typeof age !== 'number') {
throw new TypeError('Age must be a number');
}
if (age < 18) {
throw new ValidationError('You must be at least 18 years old');
}
return 'Access granted';
}
function tryToGetAccess(age) {
try {
console.log(getAccess(age));
} catch (error) {
if (error instanceof ValidationError) {
console.log('Denied:', error.message);
} else {
throw error; // not ours to handle
}
} finally {
console.log('Done');
}
}
tryToGetAccess(20); // Access granted, Done
tryToGetAccess(16); // Denied: You must be at least 18 years old, Done
tryToGetAccess('16'); // Done, then Uncaught TypeError: Age must be a number
Wrapping a low-level error and keeping the original one in cause:
async function loadUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
return await response.json();
} catch (error) {
throw new Error('Failed to load user', { cause: error });
}
}
Follow-up Questions
- What happens with
returninsidetryif there isfinally?finallystill runs, and areturninfinallyoverrides the previous one. - Does
try...catchcatch a rejected promise? Only if the promise is awaited insidetry. - How do errors work in React? Rendering errors are caught by
error boundaries, not by
try...catch.
Common Mistakes
- An empty
catchblock: the error disappears and the bug becomes invisible. - Throwing strings (
throw 'error'): there is no stack trace andinstanceofchecks don't work. - Wrapping a
setTimeoutor a promise withoutawaitintry...catchand expecting it to catch the error.