Package Exports
- modern-errors
Readme
Handle errors like it's 2022 🔮
Error handling framework that is minimalist yet featureful.
Features
- Minimalist API
- Custom error types
- Wrap any error's message, type, or properties
- Set properties on individual errors, or on all errors of the same type
- Automatically separate (unhandled) system errors from (handled) user errors
- System errors indicate where to report bugs
- Handle invalid errors (not an
Error
instance, missing stack, etc.) - Follow error handling best practices
- Based on modern JavaScript features
Example
Create the error types and handler.
// `error.js`
import modernErrors from 'modern-errors'
export const { errorHandler, InputError, AuthError, DatabaseError } =
modernErrors()
Wrap the main function with the error handler.
import { errorHandler } from './error.js'
export const main = async function (filePath) {
try {
return await readContents(filePath)
} catch (error) {
throw errorHandler(error)
}
}
Throw/re-throw errors.
import { InputError } from './error.js'
const readContents = async function (filePath) {
try {
return await readFile(filePath)
} catch (cause) {
throw new InputError(`Could not read ${filePath}`, { cause })
}
}
Install
npm install modern-errors
This package is an ES module and must be loaded using
an import
or import()
statement,
not require()
.
API
modernErrors(options?)
options
object
Return value: object
Creates the error types and handler.
Return value
Any error type
Type: ErrorType
Any error type can be retrieved from the
return value. The name must end with Error
. For example: InputError
,
AuthError
, etc.
errorHandler
Type: (anyException) => Error
Error handler that should wrap each main function.
Options
bugsUrl
Type: string | URL
URL where users should report system errors/bugs.
onCreate
Type: (error, parameters) => void
Called on any new ErrorType('message', parameters)
.
Can be used to customize error parameters or set
error type properties. By default, any parameters
are set as error properties.
Usage
Setup
Create error types and handler
✨ Retrieving the error types automatically creates them. ✨
// error.js
import modernErrors from 'modern-errors'
// The error types are examples: any name ending with "Error" can be specified
export const { errorHandler, InputError, AuthError, DatabaseError } =
modernErrors()
Error handler
Each main function should be wrapped with the errorHandler()
.
import { errorHandler } from './error.js'
export const main = async function (filePath) {
try {
return await readContents(filePath)
} catch (error) {
// `errorHandler()` returns `error`, so `throw` must be used
throw errorHandler(error)
}
}
Throw errors
Simple errors
import { InputError } from './error.js'
const validateFilePath = function (filePath) {
if (filePath === '') {
throw new InputError('Missing file path.')
}
}
Invalid errors
Invalid errors are normalized
by errorHandler()
. This includes errors that are not an
Error
instance
or that have
wrong/missing properties.
import { errorHandler } from './error.js'
export const main = function (filePath) {
try {
throw 'Missing file path.'
} catch (error) {
throw errorHandler(error) // Normalized to an `Error` instance
}
}
Re-throw errors
Errors are re-thrown using the
standard cause
parameter.
This allows wrapping the error message,
properties, or type.
import { InputError } from './error.js'
const readContents = async function (filePath) {
try {
return await readFile(filePath)
} catch (cause) {
throw new InputError(`Could not read ${filePath}`, { cause })
}
}
The errorHandler()
merges all error cause
into a
single error, including their
message
,
stack
,
name
,
AggregateError.errors
and any additional property. This ensures:
error.cause
does not need to be traversed- The stack trace is neither verbose nor redundant, while still keeping all information
Wrap error message
The outer error message is appended.
try {
await readFile(filePath)
} catch (cause) {
throw new InputError(`Could not read ${filePath}`, { cause })
// InputError: File does not exist.
// Could not read /example/path
}
If the outer error message ends with :
, it is prepended instead.
throw new InputError(`Could not read ${filePath}:`, { cause })
// InputError: Could not read /example/path: File does not exist.
:
can optionally be followed a newline.
throw new InputError(`Could not read ${filePath}:\n`, { cause })
// InputError: Could not read /example/path:
// File does not exist.
Error properties
Set error properties
Unless the onCreate()
option is defined, any parameter is set as
an error property.
const error = new InputError('Could not read the file.', { filePath: '/path' })
console.log(error.filePath) // '/path'
Wrap error properties
Pass an empty message
in order to set error properties without wrapping the
message
.
try {
await readFile(filePath)
} catch (cause) {
throw new Error('', { cause, filePath: '/path' })
}
Customize error parameters
The onCreate()
option can be used to validate and transform error
parameters
.
modernErrors({
onCreate(error, parameters) {
const { filePath } = parameters
if (typeof filePath !== 'string') {
throw new Error('filePath must be a string.')
}
const hasFilePath = filePath !== undefined
Object.assign(error, { filePath, hasFilePath })
},
})
const error = new InputError('Could not read the file.', {
filePath: '/path',
unknownParam: true,
})
console.log(error.filePath) // '/path'
console.log(error.hasFilePath) // true
console.log(error.unknownParam) // undefined
Type-specific logic
The onCreate()
option can trigger error type-specific logic.
modernErrors({
onCreate(error, parameters) {
onCreateError[error.name](error, parameters)
},
})
const onCreateError = {
InputError(error, parameters) {
// ...
},
AuthError(error, parameters) {
// ...
},
// ...
}
Error type properties
By default, error types are very similar except for their
name
.
The onCreate()
option can be used to set properties on all
instances of a given error type.
modernErrors({
onCreate(error, parameters) {
Object.assign(error, parameters, ERROR_PROPS[error.name])
},
})
const ERROR_PROPS = {
InputError: { isUser: true },
AuthError: { isUser: true },
DatabaseError: { isUser: false },
}
const error = new InputError('Could not read the file.')
console.log(error.isUser) // true
Error types
Test error type
Once errorHandler()
has been applied, the error type can be
checked by its name
(as opposed to
instanceof
).
Libraries should document their possible error names, but do not need to
export
their error types.
if (error.name === 'InputError') {
// ...
} else if (error.name === 'SystemError') {
// ...
}
Set error type
When re-throwing errors, the outer error type overrides the inner one.
try {
throw new AuthError('Could not authenticate.')
} catch (cause) {
throw new InputError('Could not read the file.', { cause })
// Now an InputError
}
However, the inner error type is kept if the outer one is Error
or
AggregateError
.
try {
throw new AuthError('Could not authenticate.')
} catch (cause) {
throw new Error('Could not read the file.', { cause })
// Still an AuthError
}
System errors
System errors/bugs can be distinguished from user errors by
handling any possible errors in try {} catch {}
and
re-throwing them
with a specific error type. The
errorHandler()
assigns the SystemError
type to any error
with an unknown type.
const getUserId = function (user) {
return user.id
}
getUserId(null) // SystemError: Cannot read properties of null (reading 'id')
Bug reports
If the bugsUrl
option is used,
modernErrors({ bugsUrl: 'https://github.com/my-name/my-project/issues' })
any system error will include the following message.
Please report this bug at: https://github.com/my-name/my-project/issues
Miscellaneous
Source maps
When using a build step (Babel, TypeScript, etc.), the error stack traces refer to the built files/lines instead of the source. This can be fixed by using source maps:
- Node.js:
--enable-source-maps
CLI flag - Chrome:
node-source-map-support
- Other browsers:
stacktrace.js
Support
For any question, don't hesitate to submit an issue on GitHub.
Everyone is welcome regardless of personal background. We enforce a Code of conduct in order to promote a positive and inclusive environment.
Contributing
This project was made with ❤️. The simplest way to give back is by starring and sharing it online.
If the documentation is unclear or has a typo, please click on the page's Edit
button (pencil icon) and suggest a correction.
If you would like to help us fix a bug or add a new feature, please check our guidelines. Pull requests are welcome!