Package Exports
- modern-errors
Readme
Handle errors like it's 2022 🔮
Error handling framework that is minimalist yet featureful.
Features
- Create 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) internal errors from (handled) user errors
- Internal errors indicate where to report bugs
- Handle invalid errors (not an
Error
instance, missing stack, etc.)
The framework integrates with other libraries to also:
- Handle CLI errors
- Use source maps on stack traces
Example
Create the error types and handler.
// `error.js`
import modernErrors from 'modern-errors'
export const {
errorHandler,
// Those error types are examples.
// Any name ending with "Error" can be specified.
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 internal 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'
export const {
errorHandler,
// Those error types are examples.
// Any name ending with "Error" can be specified.
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 types
Test error type
Once errorHandler()
has been applied, the error type can be
checked by its name
. Libraries should document their possible error names, but
do not need to export
their error types.
if (error.name === 'InputError') {
// ...
} else if (error.name === 'InternalError') {
// ...
}
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
}
Internal errors
Internal 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 InternalError
type to any error
with an unknown type.
const getUserId = function (user) {
return user.id
}
getUserId(null) // InternalError: 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 internal error will include the following message.
Please report this bug at: https://github.com/my-name/my-project/issues
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
Miscellaneous
CLI errors
CLI applications can assign a different exit code and log verbosity per error
type by using handle-cli-error
.
#!/usr/bin/env node
import handleCliError from 'handle-cli-error'
// `programmaticMain()` must use `modern-errors`'s `errorHandler`
import programmaticMain from './main.js'
const cliMain = function () {
try {
const cliFlags = getCliFlags()
programmaticMain(cliFlags)
} catch (error) {
// Print `error` then exit the process
handleCliError(error, {
types: {
InputError: { exitCode: 1, short: true },
DatabaseError: { exitCode: 2, short: true },
default: { exitCode: 3 },
},
})
}
}
cliMain()
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
Related projects
error-type
: Create custom error typeserror-serializer
: Convert errors to/from plain objectsnormalize-exception
: Normalize exceptions/errorsmerge-error-cause
: Merge an error with itscause
error-cause-polyfill
: Polyfillerror.cause
handle-cli-error
: 💣 Error handler for CLI applications 💥
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!