In this post, when I say "error", I'm referring to a bug that was found in runtime, and when I say "exception", I'm referring to an operation that failed to complete.
Javascript does not have any standard way of dealing with exceptions - this splinters the community with different packages using different solutions to this problem.
For example, at work, I use an ldap-js npm package. Many of their exposed functions can cause a number of different exceptions to be thrown. These exceptions are all subclasses of Error
, and are all exported together (as can be seen in their documentation here).
I also use node, and node's fs module will throw different exceptions by just throwing an instance of Error(), and attaching custom properties to it. For example, a file-not-found error can be distinguished from other errors by checking if the error instance has a code property that equals "ENOENT".
I propose we design a standard way to throw exceptions. The solution I'll propose here would be to add a new Exception class that inherits from Error
Here's an example Exception implementation:
class Exception extends Error {
constructor(code, message = code) {
super(message)
this.code = code
this.name = this.constructor.name
}
}
Exampe usage:
function getResource() {
if (resource) return resource
throw new Exception('NotFound', 'Resource was not found.')
}
function getResourceOrDefault(defaultValue = null) {
try {
return getResource()
} catch (ex) {
if (!(ex instanceof Exception)) throw ex
if (ex.code !== 'NotFound') throw ex
return defaultValue
}
}
Note that in the example implementation of Exception
I set this.name
to this.constructor.name
. This makes inheriting Exception much easier, as a subclass will automatically receive the class name as the name property. Inheritance is important to preserve compatibility with existing exception solutions, among other reasons.
// A package that used to make all of their exception inherit from Error ...
class CustomError extends Error {
constructor(message) {
super(message)
this.name = 'CustomError'
}
}
// ... can now just have their exceptions inherit from Exception,
// without making any breaking changes
class CustomError extends Error {
constructor(message) {
super('CustomError', message)
// this.name will automatically be set to 'CustomError'
}
}
Having a standard Exception solution can be beneficial for a number of reasons. One of which is that it'll be easier to make helper exception functions, because everyone follows the same standard. For example:
function ignoreExceptionOfType(code, fn) {
return (...args) => {
try {
return fn(...args)
} catch (ex) {
if (ex instanceof Exception && ex.code === code) {
return null
}
throw ex
}
}
}
A helper function like this wouldn't be very possible if people keep using their own custom solutions for exception handling.