0.0.14 • Published 7 years ago

con-error v0.0.14

Weekly downloads
3
License
Zlib
Repository
github
Last release
7 years ago

ConError

Contextual, Chainable JavaScript Errors

Build Status Coverage Status

Features

Basic features

  • rethrow and chain errors
  • include an object with values from the point of an error
  • behaves similar to a rejected Promise and can be used as one
  • no dependencies

Stack traces

As an example, a call chain may be get <- load <- refreshCart <- refreshScreen. If every method catches and rethrows with additional context, the resulting ConError would print the stack trace:

ConError: failed to refresh screen
Error
  at refreshScreen (screen.js:52:8)
Caused by: ConError: failed to refresh cart
Error
  at refreshCart (cart.js:52:8)
  at refreshScreen (screen.js:50:8)
Caused by: ConError: error getting cart details
  context: {
    cartId: 1
  }
Error
  at load (cart-details.js:52:8)
  at refreshCart (cart.js:50:8)
  at refreshScreen (screen.js:50:8)
Caused by: ConError: http error
  context: {
    statusCode: 500,
    response: [a large Object],
    cartId: 1,
    sessionId: 'e30d...'
  }
Error
  at handleResponse (http.js:37:8)
  at sendReq (http.js:50:10)
  at get (http.js:80:8)
  at load (cart-details.js:50:8)
  at refreshCart (cart.js:50:8)
  at refreshScreen (screen.js:50:8)

API

Basic Usage

const ConError = require('con-error');
try {
  doSomething();
} catch (err) {
  throw new ConError(err, 'failed to do something', {
      userId: userId,
      service: service
    });
}

new ConError(cause: (Error|Error[])?, message: string?, context: {}?)

ConError accepts an Error or array of Errors, a message, and an arbitrary object. All values are optional, but any that are given must appear in the expected order.

All three parameters have distinct purposes in diagnosing errors:

  • causes (nested errors) hold the state for where the original error occurred at a lower level
  • message gives a general description of what was being attempted in the frame this error was thrown from, and may help for locating the error without sourcemaps
  • context gives a snapshot of the local variable state of the frame this error was thrown from

ConError makes a deep clone of the given contextual object on creation. This is to avoid problems where values visible to the ConError creation frame are altered before the ConError is printed or inspected in a debugger. With very large objects, you may want to limit the properties given as context. This should be also be done to minimize noise.

Typical usage would be:

try {
  doSomething();
} catch (e) {
  throw new ConError(e, 'error in some process', locals);
}

Usage as a rejected Promise, chaining a previous error:

return promise
  .then(account => nextStep(account))
  .catch(e => new ConError(e, 'failed in nextStep to register user', {
    email: email
  }));

.causes

A list of nested errors, if any. If there are none, this is an empty array.

.message

The message the ConError was constructed with. If there was none, this is an empty string.

.context

A deep clone of the contextual object the ConError was constructed with. If there was none, this is an empty object.

.stack

The stack of this error as a string. This is stack unmodified from the point of capture and the format varies by JS engine.

Because the stack is taken from an Error captured internally, this stack will contain an additional line inside ConError.

.formats()

Returns a CeFormats instance to produce outputs for this ConError.

.toString()

Returns a string containing the entire stack trace of the ConError instance. At each level in the hierarchy, it will only select and stringify the first cause. This is the same as cerr.formats().string().

To stringify all sequences of errors, cerr.sequences().all().map(e => e.toString())

.sequences()

Returns a new CeSequences instance for this ConError.

.throw()

.throw does as it says, it will throw the ConError.

Can be used when the throw keyword is not permitted, e.g., in a ternary:

const clampTo100 = number =>
  number > 0 ?
    Math.min(number, 100) :
    new ConError('number not positive', {number: number}).throw();

Promise-like behavior

The existence of the .then function makes ConError appear Promise-like to scripts. The return value of .then is a native es6 Promise (or polyfill) and can be chained as usual.

.then(resolveFn?, rejectFn?)

The first callback of .then is never called, since a ConError instance is always rejected.

When a rejected callback is given, this is called after the interpreter has settled. The ConError instance is given as the argument to the callback.

This will call applyOption with the resolved value of defaultOption:

fnThatReturnsConError()
  .then(opt => opt, () => defaultOption)
  .then(opt => applyOption(opt));

.catch(rejectFn)

The callback is always called after the interpreter has settled.

The return value is a native Promise which will be resolved or rejected based on the callback.

Caveats

With native Promises, there are subtle differences. The EcmaScript standard checks for internal states which cannot be set by script.

Promise.resolve(conError) === conError; // true with some Promise libraries
Promise.resolve(conError) === conError; // false with native Promise

The inheritance chain of ConError is an Error and not a Promise. As a result, testing with instanceof will indicate it is not a Promise.

conError instanceof ConError; // true
conError instanceof Error; // true
conError instanceof Promise; // false

When the differences are important, one can easily make it a native Promise:

const nooped = new ConError().then();
Promise.resolve(nooped) === nooped; // true
nooped instanceof Promise; // true

CeFormats

Created by a ConError with .formats() to encode the error for output.

.string()

Returns a form of the ConError that is formatted as a plain string.

.object()

Returns a form of the ConError as a plain JavaScript object. That is, an object with no values that cannot be encoded as JSON. i.e., no functions, no DOM elements, etc.

This is the same object that is encoded by .json().

.json(options: {}?)

Returns a form of the ConError object serialized as JSON with the following form:

{
  "name": "ConError",
  "message": "",
  "context": {},
  "stack": "",
  "causes": [
    {
      "name": "Error",
      "message": "",
      "stack": ""
    }
  ]
}

json options

indent: number, default 0

number of spaces to indent each line, 0 = compact form

cerr.formats().json({indent: 2});

CeSequences

Produces sequences for the referent ConError.

A sequence is an array of errors where each error in the sequence is caused by the next error in the sequence. e.g., the first element of a sequence may be thrown by an on-click handler, and the last element may be an error from a remote service call initiated by the on-click handler. [OnClickError, ServiceError].

Sequences are returned as an array of Error objects, starting with the referent ConError and possibly ending with a thrown object that is the final cause. e.g., [ConError, ConError, TypeError].

.first()

This returns an array of Errors following the first cause of every ConError instance, and possibly including another thrown object as the final cause.

.all()

This returns an unsorted array of all sequences representing a full stack trace from a root cause up to the referent error.

More specifically, where a "root cause" is either a ConError with no cause, or another thrown type:

  • every array's first element is the referent ConError
  • each element in an array is the cause of the previous element
  • every array ends with an original root cause
  • every root cause is returned in exactly one array

For example, if B failed because of two async functions, C and D, the returned array could contain two arrays of error sequences like this:

before: [ A <- [ B <- E, C <- D ] ]

after: [
  [A, B, E],
  [A, C, D],
]
0.0.14

7 years ago

0.0.13

7 years ago

0.0.12

7 years ago

0.0.11

7 years ago

0.0.10

7 years ago

0.0.9

7 years ago

0.0.8

7 years ago

0.0.7

7 years ago

0.0.6

7 years ago

0.0.5

7 years ago

0.0.4

7 years ago

0.0.3

7 years ago

0.0.2

7 years ago

0.0.1

7 years ago