# error Wrap errors with more context. ## Inspiration This module is inspired by the go error libraries that have simple functions for creating & wrapping errors. This is based on libraries like [eris][eris] & [pkg/errors][pkg-errors] ## Older version of `error` If you are looking for the older v7 version of error you should check [v7.x][7.x] branch ## Using `error` with `async` / `await` Check out [`resultify`](https://www.npmjs.com/package/resultify) ! The rest of the examples use plain vanilla callbacks. ## Motivation Wrapping errors when bubbling up instead of just doing `if (err) return cb(err)` allows you to pass more context up the stack. Common example include passing along parameters from the DB read related to the failure or passing along any context from the user in a HTTP request when doing a failure. This can give you nice to read messages that include more information about the failure as it bubbles up. There is more information about how to handle errors in this article [Don't just check errors, handle them gracefully][dave] If you want a deep dive into the difference between [Programming and Operational errors](https://www.joyent.com/node-js/production/design/errors) please check out [this guide](https://www.joyent.com/node-js/production/design/errors) examples: ```js const { wrapf } = require('error') function authenticatRequest(req) { authenticate(req.user, (err) => { if (err) { return cb(wrapf('authenticate failed', err)) } cb(null) }) } ``` or ```js const { wrapf } = require('error') function readFile(path, cb) { fs.open(path, 'r', (err, fd) => { if (err) { return cb(wrapf('open failed', err, { path })) } const buf = Buffer.alloc(64 * 1024) fs.read(fd, buf, 0, buf.length, 0, (err) => { if (err) { return cb(wrapf('read failed', err, { path })) } fs.close(fd, (err) => { if (err) { return cb(wrapf('close failed', err, { path })) } cb(null, buf) }) }) }) } ``` ## Structured errors ```js const { SError } = require('error') class ServerError extends SError {} class ClientError extends SError {} const err = ServerError.create( '{title} server error, status={statusCode}', { title: 'some title', statusCode: 500 } ) const err2 = ClientError.create( '{title} client error, status={statusCode}', { title: 'some title', statusCode: 404 } ) ``` ## Wrapped Errors ```js const net = require('net'); const { WError } = require('error') class ServerListenError extends WError {} var server = net.createServer(); server.on('error', function onError(err) { if (err.code === 'EADDRINUSE') { throw ServerListenFailedError.wrap( 'error in server, on port={requestPort}', err, { requestPort: 3000, host: null } ) } else { throw err; } }); server.listen(3000); ``` ## Comparison to Alternatives. There are alternative existing libraries for creating typed and wrapped errors on npm. Here's a quick comparison to some alternatives. ### [`verror`][verror] This module takes inspiration from `verror` and adds improvements. - You can pass extra fields as meta data on the error - The templating forces dynamic strings to be extra fields. - Uses ES6 classes for inheritance. This gives your errors unique class names and makes them show up in heapdumps. - Has JSON.stringify support ### [`error@7.x`][7.x] This package used to have a completely different API on the [7.x][7.x] branch. - New `error` module uses actual classes instead of dynamically monkey patching fields onto `new Error()` - Implementation is more static, previous code was very dynamic - Simpler API, see the message & properties in one place. - `wrapf` & `errorf` helpers for less boilerplate. ### Hand writing `Error` sub classes. You can create your own Error classes by hand. This tends to lead to 10-20 lines of boilerplate per error which is replace with one line by using the `error` module; aka ```js class AccountsServerFailureError extends SError {} class ConnectionResetError extends WError {} ``` ### [`ono`][ono] The `ono` package has similar functionality with a different API - `ono` encourages plain errors instead of custom errors by default - `error` has zero dependencies - `error` is only one simple file. `ono` is 10. - `error` implementation is more static, ono is very dynamic. ## Documentation This package implements three classes, `WError`; `SError` & `MultiError` You are expected to subclass either `WError` or `SError`; - `SError` stands for `Structured Error`; it's an error base class for adding informational fields to your error beyond just having a message. - `WError` stands for `Wrapped Error`; it's an error base class for when you are wrapping an existing error with more information. The `MultiError` class exists to store an array of errors but still return a single `Error`; This is useful if your doing a parallel operation and you want to wait for them all to finish and do something with all of the failures. Some utility functions are also exported: - `findCauseByName`; See if error or any of it's causes is of the type name. - `fullStack`; Take a wrapped error and compute a full stack. - `wrapf`; Utility function to quickly wrap - `errorf`; Utility function to quickly create an error - `getInfo`; Utility function to get the info for any error object. Calls `err.info()` if the method exists. ### `WError` Example: ```js class ServerListenError extends WError {} ServerListenError.wrap('error in server', err, { port: 3000 }) ``` When using the `WError` class it's recommended to always call the static `wrap()` method instead of calling the constructor directly. Example (without cause message): ```js class ApplicationStartupError extends WError {} ApplicationStartupError.wrap( 'Could not start the application cleanly: {reason}', err, { skipCauseMessage: true, reason: 'Failed to read from disk' } ) ``` Setting `skipCauseMessage: true` will not append the cause error message but still make the cause object available. ### `const werr = new WError(message, cause, info)` Internal constructor, should pass a `message` string, a `cause` error and a `info` object (or `null`). ### `WError.wrap(msgTmpl, cause, info)` `wrap()` method to create error instances. This applies the [`string-template`][string-template] templating to `msgTmpl` with `info` as a parameter. The `cause` parameter must be an `error` The `info` parameter is an object or `null`. The `info` parameter can contain the field `skipCauseMessage: true` which will make `WError` not append `: ${causeMessage}` to the message of the error. ### `werr.type` The `type` field is the machine readable type for this error. Always use `err.type` and never `err.message` when trying to determine what kind of error it is. The `type` field is unlikely to change but the `message` field can change. ### `werr.fullType()` Calling `fullType` will compute a full type for this error and any causes that it wraps. This gives you a long `type` string that's a concat for every wrapped cause. ### `werr.cause()` Returns the `cause` error. ### `werr.info()` Returns the `info` object passed on. This is merged with the info of all `cause` errors up the chain. ### `werr.toJSON()` The `WError` class implements `toJSON()` so that the JSON serialization makes sense. ### `WError.fullStack(err)` This returns a full stack; which is a concatenation of this stack trace and the stack trace of all causes in the cause chain ### `WError.findCauseByName(err, name)` Given an err and a name will find if the err or any causes implement the type of that name. This allows you to check if a wrapped `ApplicationError` has for example a `LevelReadError` or `LevelWriteError` in it's cause chain and handle database errors differently from all other app errors. ### `SError` Example: ```js class LevelReadError extends SError {} LevelReadError.create('Could not read key: {key}', { key: '/some/key' }) ``` When using the `SError` class it's recommended to always call the static `create()` method instead of calling the constructor directly. ### `const serr = new SError(message, info)` Internal constructor that takes a message string & an info object. ### `SError.create(messageTmpl, info)` The main way to create error objects, takes a message template and an info object. It will use [string-template][string-template] to apply the template with the `info` object as a parameter. ### `SError.getInfo(error)` Static method to `getInfo` on a maybe error. The `error` can be `null` or `undefined`, it can be a plain `new Error()` or it can be a structured or wrapped error. Will return `err.info()` if it exists, returns `{}` if its `null` and returns `{ ...err }` if its a plain vanilla error. ### `serr.type` Returns the type field. The `err.type` field is machine readable. Always use `err.type` & not `err.message` when trying to compare errors or do any introspection. The `type` field is unlikely to change but the `message` field can change. ### `serr.info()` Returns the info object for this error. ### `serr.toJSON()` This class can JSON serialize cleanly. ### `MultiError` Example: ```js class FanoutError extends MultiError {} function doStuff (filePath, cb) { fanoutDiskReads(filePath, (errors, fileContents) => { if (errors && errors.length > 0) { const err = FanoutError.errorFromList(errors) return cb(err) } // do stuff with files. }) } ``` When using the `MultiError` class it's recommended to always call the static `errorFromList` method instead of calling the constructor directly. ## Usage from typescript The `error` library does not have an `index.d.ts` but does have full `jsdoc` annotations so it should be typesafe to use. You will need to configure your `tsconfig` appropiately ... ```json { "compilerOptions": { ... "allowJs": true, ... }, "include": [ "src/**/*.js", "node_modules/error/index.js" ], "exclude": [ "node_modules" ] } ``` Typescript does not understand well type source code in `node_modules` without an `index.d.ts` by default, so you need to tell it to include the implementation of `error/index.js` during type checking and to `allowJs` to enable typechecking js + jsdoc comments. ## Installation `npm install error` ## Contributors - Raynos ## MIT Licenced [eris]: https://github.com/rotisserie/eris/tree/v0.1.0 [pkg-errors]: https://github.com/pkg/errors [7.x]: https://github.com/Raynos/error/tree/v7.x [dave]: https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully [string-template]: https://github.com/Matt-Esch/string-template [verror]: https://github.com/joyent/node-verror [ono]: https://github.com/JS-DevTools/ono