Every error JavaScript throws is an Error object. It carries the information you need to understand and handle the problem: what kind of error it is, what went wrong, where it happened, and sometimes what caused it. You can create these objects yourself, too, including your own custom error types.
try {
null.length;
} catch (err) {
console.log(err.name);
console.log(err.message);
console.log(String(err)); // "name: message"
}
| Property | Holds |
|---|---|
| name | The type of error, like "TypeError" |
| message | What went wrong, as text |
| stack | Where it happened: the chain of function calls, with line numbers |
| cause | The original error that led to this one (optional, 2022) |
stack is a string starting with the error itself, followed by one line per function call that led to it, most recent first. It is exactly what the console shows under an uncaught error:
function inner() { return null.x; }
function outer() { return inner(); }
try {
outer();
} catch (err) {
console.log(typeof err.stack);
console.log(err.stack.split("\n")[0]);
}
The following lines look like at inner (script.js:1:33) then at outer (script.js:2:33): the error happened in inner, which was called by outer. The exact format differs between browsers.
stack as "Firefox only." Every major browser (Chrome, Edge, Firefox, Safari) supports stack; only the text format differs.
Create an error with new Error(message). Calling Error(message) without new does the same thing. Each built-in type has its own constructor, and every one of them is also an Error:
const e1 = new Error("Something failed");
const e2 = new RangeError("Too big");
console.log(e1.name, e2.name);
console.log(e2 instanceof RangeError, e2 instanceof Error);
console.log(String(e2));
| Error | A general error (the parent of all the others) |
| ReferenceError | A name that does not exist |
| TypeError | A value used in a way its type does not allow |
| RangeError | A number outside the allowed range |
| SyntaxError | Code or JSON that breaks the grammar rules |
| URIError | Broken URL encoding in decodeURI and similar |
| AggregateError | Several errors wrapped together (2021) |
| EvalError | Legacy: no longer thrown by modern JavaScript |
AggregateError is built in, and name is an ordinary property, so custom errors (below) can have any name you like.
When you catch a low-level error and throw a clearer one, pass the original as cause so no information is lost:
function loadSettings(text) {
try {
return JSON.parse(text);
} catch (err) {
throw new Error("Could not load settings", { cause: err });
}
}
try {
loadSettings("{oops");
} catch (err) {
console.log(err.message);
console.log(err.cause.name);
}
Error.isError(x) returns true only for real Error objects. Unlike instanceof Error, it also works for errors that come from another window or frame, and it is not fooled by a plain object that just has the same properties:
console.log(Error.isError(new TypeError("x")));
console.log(Error.isError({ name: "Error", message: "x" }));
console.log(Error.isError("x"));
It is very new, so older browsers do not have it. err instanceof Error works everywhere for everyday code.
You can make your own error type, so your code can tell "the user typed something wrong" apart from a real bug. It uses two pieces of class syntax this course has not covered: extends Error means "a ValidationError is a kind of Error, with everything an Error has," and super(message) runs Error's own setup so message and stack are filled in:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
try {
throw new ValidationError("Email is required", "email");
} catch (err) {
console.log(String(err));
console.log(err.field);
console.log(err instanceof ValidationError, err instanceof Error);
}
With custom types, a catch block can respond to each kind of problem differently, and pass on anything unexpected (JS Error Statements):
function save(form) {
try {
if (!form.email) throw new ValidationError("Email is required", "email");
return "Saved";
} catch (err) {
if (err instanceof ValidationError) {
return "Please fix the " + err.field + " field";
}
throw err;
}
}
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
console.log(save({ email: "ava@school.org" }));
console.log(save({}));
In older code you may see properties that only one browser ever had. Do not use them; they will not work for most of your users:
| Property | Where it works | Use instead |
|---|---|---|
| description, number | Old Microsoft browsers only | Use message and name |
| fileName, lineNumber, columnNumber | Firefox only | Read stack instead |
| toSource() | Removed | Use String(err) |
| arguments, caller | Deprecated function properties | Not needed |
What does this log?
const e = new RangeError("Too big");
console.log(e.name, e instanceof Error, String(e));
RangeError true RangeError: Too big. A RangeError is a kind of Error, and converting an error to a string gives "name: message".Which property holds the text you passed to new Error("...")?
namemessagestackdescriptionname is the type, stack is where it happened, and description is a non-standard Microsoft property.Why pass { cause: err } when you throw a new, clearer error?
cause keeps the low-level details (its name, message, and stack) for debugging.name, message, and the first line of stackJSON.parse failure in a new Error with a cause, then log both messagesValidationError class and throw it for a missing usernamecatch that handles ValidationError and rethrows everything else