The Error
class includes debugging information (as properties of its instances), such as the error's call stack. JS interpreters know how to serialise those properties into an informative error message string, and they can also be consumed by debugging software - such as browser dev tools - to construct a more informative GUI representation of the error. This is why it's generally more useful to throw an instance of the Error
class rather than simply throwing, for example, a string describing the error, or a number representing an error code.
Using custom errors
It's particularly useful to make your own subclasses of Error
, which allow you to uniquely identify different types of error with a descriptive name and machine-readable...
- clues for debugging,
- information for better user-facing error messages, or
- information to help recover from the error.
Then, when you're handling errors, you can use the nice and clean instanceof
operator to check what kind of error occurred. e.g.:
class DangerousWaterCurrent extends Error {
constructor(waterSpeed){
super(`These waters are moving at ${waterSpeed} metres per second - too fast to cross!`) // Provide a `message` argument to the Error() constructor
this.waterSpeed = waterSpeed // This passes some context about why/how the error occurred back to whichever function is going to catch & handle it
}
}
// ...later...
try {
swimAcrossRiver(river)
} catch (thrownValue) {
if (thrownValue instanceof DangerousWaterCurrent) {
if (thrownValue.waterSpeed <= 3){
paddleKayak(river)
} else {
constructBridge(river)
}
} else {
throw thrownValue // "Re-throw" the error back up the execution chain, for someone else to handle
}
}
new Error()
vs Error()
There is a "convenient" shorthand way to make an instance of Error
: by calling Error(message)
, instead of new Error(message)
, the way you'd make an instance of a normal class. This is a deliberate exception, inserted by the language designers, to the rule. There are similar shorthands for other in-language classes, like Number()
and String()
. They also let you call these classes with ()
as if they were functions, not classes. JS doesn't allow normal classes to do this, even though they're all actually functions under the syntactical sugar of "classes". Try in a REPL:
> class E extends Error {}
undefined
> Error(); 'a value'
"a value"
> E(); 'a value'
Uncaught TypeError: Class constructor E cannot be invoked without 'new'
at <anonymous>:2:1
A broader opinion on design: Personally, I think this design decision was a mistake, as it adds more exceptions to the rules of JavaScript - which means more to learn for programmers, and more for language translators/interpreters to account for. Instead of C++/Java's new
keyword, simply calling a class as if it were a function (as in Number("abc123")
) should have the properties that the new
keyword currently has: the class's constructor
function should be executed, with this
bound to the instance. The new
keyword could then be discarded from the language. This is how Python's syntax works, and it's simpler, more readable, and more convenient.