mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Update README for v3
This commit is contained in:
parent
09cd72d51c
commit
f6eb185f84
6 changed files with 1801 additions and 75 deletions
|
@ -1,96 +1,424 @@
|
||||||
# @overleaf/o-error
|
# @overleaf/o-error
|
||||||
|
|
||||||
|
[![npm version](https://badge.fury.io/js/%40overleaf%2Fo-error.svg)](https://badge.fury.io/js/%40overleaf%2Fo-error)
|
||||||
[![CircleCI](https://circleci.com/gh/overleaf/o-error.svg?style=svg)](https://circleci.com/gh/overleaf/o-error)
|
[![CircleCI](https://circleci.com/gh/overleaf/o-error.svg?style=svg)](https://circleci.com/gh/overleaf/o-error)
|
||||||
|
|
||||||
Make custom error classes that:
|
Light-weight helpers for handling JavaScript Errors in node.js and the browser.
|
||||||
|
|
||||||
- pass `instanceof` checks,
|
- Get long stack traces across async functions and callbacks with `OError.tag`.
|
||||||
- have stack traces,
|
- Easily make custom `Error` subclasses, optionally with HTTP status codes.
|
||||||
- support custom messages and properties (`info`), and
|
- Wrap internal errors, preserving the original errors for logging as `causes`.
|
||||||
- can wrap internal errors (causes) like [VError](https://github.com/joyent/node-verror).
|
- Play nice with error logging services by keeping data in attached `info` objects instead of the error message.
|
||||||
|
|
||||||
ES6 classes make it easy to define custom errors by subclassing `Error`. Subclassing `OError` adds a few extra helpers.
|
## Table of Contents
|
||||||
|
|
||||||
## Usage
|
<!-- toc -->
|
||||||
|
|
||||||
### Throw an error directly
|
- [Long Stack Traces with `OError.tag`](#long-stack-traces-with-oerrortag)
|
||||||
|
* [The Problem](#the-problem)
|
||||||
|
* [The Solution](#the-solution)
|
||||||
|
* [Adding More Info](#adding-more-info)
|
||||||
|
* [`async`/`await`](#asyncawait)
|
||||||
|
* [Better Async Stack Traces in Node 12+](#better-async-stack-traces-in-node-12)
|
||||||
|
- [Create Custom Error Classes](#create-custom-error-classes)
|
||||||
|
* [Attaching Extra Info](#attaching-extra-info)
|
||||||
|
* [Wrapping an Internal Error](#wrapping-an-internal-error)
|
||||||
|
- [OError API Reference](#oerror-api-reference)
|
||||||
|
* [new OError(message, [info], [cause])](#new-oerrormessage-info-cause)
|
||||||
|
* [oError.withInfo(info) ⇒ this](#oerrorwithinfoinfo--this)
|
||||||
|
* [oError.withCause(cause) ⇒ this](#oerrorwithcausecause--this)
|
||||||
|
* [OError.tag(error, [message], [info]) ⇒ Error](#oerrortagerror-message-info--error)
|
||||||
|
* [OError.getFullInfo(error) ⇒ Object](#oerrorgetfullinfoerror--object)
|
||||||
|
* [OError.getFullStack(error) ⇒ string](#oerrorgetfullstackerror--string)
|
||||||
|
- [References](#references)
|
||||||
|
|
||||||
|
<!-- tocstop -->
|
||||||
|
|
||||||
|
## Long Stack Traces with `OError.tag`
|
||||||
|
|
||||||
|
### The Problem
|
||||||
|
|
||||||
|
While JavaScript errors have stack traces, they only go back to the start of the latest tick, so they are often not very useful. For example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const OError = require('@overleaf/o-error')
|
const demoDatabase = {
|
||||||
|
findUser(id, callback) {
|
||||||
|
process.nextTick(() => {
|
||||||
|
// return result asynchronously
|
||||||
|
if (id === 42) {
|
||||||
|
callback(null, { name: 'Bob' })
|
||||||
|
} else {
|
||||||
|
callback(new Error('not found'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
function doSomethingBad() {
|
function sayHi1(userId, callback) {
|
||||||
throw new OError({
|
demoDatabase.findUser(userId, (err, user) => {
|
||||||
message: 'did something bad',
|
if (err) return callback(err)
|
||||||
info: { thing: 'foo' },
|
callback(null, 'Hi ' + user.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
doSomethingBad()
|
|
||||||
// =>
|
sayHi1(43, (err, result) => {
|
||||||
// { OError: did something bad
|
if (err) {
|
||||||
// at doSomethingBad (repl:2:9) <-- stack trace
|
console.error(err)
|
||||||
// name: 'OError', <-- default name
|
} else {
|
||||||
// info: { thing: 'foo' } } <-- attached info
|
console.log(result)
|
||||||
|
}
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### Custom error class
|
The resulting error's stack trace doesn't make any mention of our `sayHi1` function; it starts at the `nextTick` built-in:
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: not found
|
||||||
|
at process.nextTick (repl:8:18)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:61:11)
|
||||||
|
```
|
||||||
|
|
||||||
|
In practice, it's often even worse, like
|
||||||
|
|
||||||
|
```
|
||||||
|
DBError: socket connection refused
|
||||||
|
at someObscureLibraryFunction (...)
|
||||||
|
at ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### The Solution
|
||||||
|
|
||||||
|
Before passing the error to a callback, call the `OError.tag` function to capture a stack trace at the call site:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class FooError extends OError {
|
const OError = require('.')
|
||||||
constructor(options) {
|
|
||||||
super({ message: 'failed to foo', ...options })
|
function sayHi2(userId, callback) {
|
||||||
}
|
demoDatabase.findUser(userId, (err, user) => {
|
||||||
|
if (err) return callback(OError.tag(err))
|
||||||
|
callback(null, 'Hi ' + user.name)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function doFoo() {
|
sayHi2(43, (err, result) => {
|
||||||
throw new FooError({ info: { foo: 'bar' } })
|
if (err) {
|
||||||
|
console.error(OError.getFullStack(OError.tag(err)))
|
||||||
|
} else {
|
||||||
|
console.log(result)
|
||||||
}
|
}
|
||||||
doFoo()
|
})
|
||||||
// =>
|
|
||||||
// { FooError: failed to foo
|
|
||||||
// at doFoo (repl:2:9) <-- stack trace
|
|
||||||
// name: 'FooError', <-- correct name
|
|
||||||
// info: { foo: 'bar' } } <-- attached info
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Wrapping an inner error (cause)
|
And use `OError.getFullStack` to reconstruct the full stack, including the tagged errors:
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: not found
|
||||||
|
at process.nextTick (repl:8:18)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:61:11)
|
||||||
|
TaggedError
|
||||||
|
at demoDatabase.findUser (repl:3:37)
|
||||||
|
at process.nextTick (repl:8:9)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:61:11)
|
||||||
|
TaggedError
|
||||||
|
at sayHi2 (repl:3:46)
|
||||||
|
at demoDatabase.findUser (repl:3:21)
|
||||||
|
at process.nextTick (repl:8:9)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:61:11)
|
||||||
|
```
|
||||||
|
|
||||||
|
The full stack contains the original error's stack and also the `TaggedError` stacks. There's some redundancy, but it's better to have too much information than too little.
|
||||||
|
|
||||||
|
### Adding More Info
|
||||||
|
|
||||||
|
You can add more information at each `tag` call site: a message and an `info` object with custom properties.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function doFoo2() {
|
function sayHi3(userId, callback) {
|
||||||
try {
|
demoDatabase.findUser(userId, (err, user) => {
|
||||||
throw new Error('bad')
|
if (err) return callback(OError.tag(err, 'failed to find user', { userId }))
|
||||||
} catch (err) {
|
callback(null, 'Hi ' + user.name)
|
||||||
throw new FooError({ info: { foo: 'bar' } }).withCause(err)
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
doFoo2()
|
sayHi3(43, (err, result) => {
|
||||||
// =>
|
if (err) {
|
||||||
// { FooError: failed to foo: bad <-- combined message
|
OError.tag(err, 'failed to say hi')
|
||||||
// at doFoo2 (repl:5:11) <-- stack trace
|
console.error(OError.getFullStack(err))
|
||||||
// name: 'FooError', <-- correct name
|
console.error(OError.getFullInfo(err))
|
||||||
// info: { foo: 'bar' }, <-- attached info
|
} else {
|
||||||
// cause: <-- the cause (inner error)
|
console.log(result)
|
||||||
// Error: bad <-- inner error message
|
|
||||||
// at doFoo2 (repl:3:11) <-- inner error stack trace
|
|
||||||
// at repl:1:1
|
|
||||||
// ...
|
|
||||||
|
|
||||||
try {
|
|
||||||
doFoo2()
|
|
||||||
} catch (err) {
|
|
||||||
console.log(OError.getFullStack(err))
|
|
||||||
}
|
}
|
||||||
// =>
|
})
|
||||||
// FooError: failed to foo: bad
|
|
||||||
// at doFoo2 (repl:5:11)
|
|
||||||
// at repl:2:3
|
|
||||||
// ...
|
|
||||||
// caused by: Error: bad
|
|
||||||
// at doFoo2 (repl:3:11)
|
|
||||||
// at repl:2:3
|
|
||||||
// ...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `OError.getFullInfo` property merges all of the `info`s from the tags together into one object. This logs a full stack trace with `failed to ...` annotations and an `info` object that contains the `userId` that it failed to find:
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: not found
|
||||||
|
at process.nextTick (repl:8:18)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:61:11)
|
||||||
|
TaggedError: failed to find user
|
||||||
|
at demoDatabase.findUser (repl:3:37)
|
||||||
|
at process.nextTick (repl:8:9)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:61:11)
|
||||||
|
TaggedError: failed to say hi
|
||||||
|
at sayHi3 (repl:3:12)
|
||||||
|
at demoDatabase.findUser (repl:3:21)
|
||||||
|
at process.nextTick (repl:8:9)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:61:11)
|
||||||
|
|
||||||
|
{ userId: 43 }
|
||||||
|
```
|
||||||
|
|
||||||
|
Logging this information (or reporting it to an error monitoring service) hopefully gives you a good start to figuring out what went wrong.
|
||||||
|
|
||||||
|
### `async`/`await`
|
||||||
|
|
||||||
|
The `OError.tag` approach works with both async/await and callback-oriented code. When using async/await, the pattern is to catch an error, tag it and rethrow:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const promisify = require('util').promisify
|
||||||
|
demoDatabase.findUserAsync = promisify(demoDatabase.findUser)
|
||||||
|
|
||||||
|
async function sayHi4(userId) {
|
||||||
|
try {
|
||||||
|
const user = await demoDatabase.findUserAsync(userId)
|
||||||
|
return `Hi ${user.name}`
|
||||||
|
} catch (error) {
|
||||||
|
throw OError.tag(error, 'failed to find user', { userId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
await sayHi4(43)
|
||||||
|
} catch (error) {
|
||||||
|
OError.tag(error, 'failed to say hi')
|
||||||
|
console.error(OError.getFullStack(error))
|
||||||
|
console.error(OError.getFullInfo(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
The resulting full stack trace points to `sayHi4` in `main`, as expected:
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: not found
|
||||||
|
at process.nextTick (repl:8:18)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:61:11)
|
||||||
|
TaggedError: failed to find user
|
||||||
|
at sayHi4 (repl:6:18)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:68:7)
|
||||||
|
TaggedError: failed to say hi
|
||||||
|
at main (repl:5:12)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:68:7)
|
||||||
|
|
||||||
|
{ userId: 43 }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Better Async Stack Traces in Node 12+
|
||||||
|
|
||||||
|
The above output is from node 10. Node 12 has improved stack traces for async code that uses native promises. However, until your whole stack, including all libraries, is using async/await and native promises, you're still likely to get unhelpful stack traces. So, the tagging approach still adds value, even in node 12. (And the `info` from tagging can add value even to a good stack trace, because it can contain clues about the input the caused the error.)
|
||||||
|
|
||||||
|
## Create Custom Error Classes
|
||||||
|
|
||||||
|
Broadly speaking, there are two kinds of errors: those we try to recover from, and those for which we give up (i.e. a 5xx response in a web application). For the latter kind, we usually just want to log a message and stack trace useful for debugging, which `OError.tag` helps with.
|
||||||
|
|
||||||
|
To recover from an error, we usually need to know what kind of error it was and perhaps to check some of its properties. Defining a custom Error subclass is a good way to do this. Callers can check the type of the error either with `instanceof` or using a custom property, such as `code`.
|
||||||
|
|
||||||
|
With ES6 classes, creating a custom error subclass is mostly as simple as `extends Error`. One extra line is required to set the error's `name` appropriately, and inheriting from `OError` handles this implementation detail. Here's an example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class UserNotFoundError extends OError {
|
||||||
|
constructor() {
|
||||||
|
super('user not found')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw new UserNotFoundError()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`instanceof Error: ${error instanceof Error}`)
|
||||||
|
console.error(
|
||||||
|
`instanceof UserNotFoundError: ${error instanceof UserNotFoundError}`
|
||||||
|
)
|
||||||
|
console.error(error.stack)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
instanceof Error: true
|
||||||
|
instanceof UserNotFoundError: true
|
||||||
|
UserNotFoundError: user not found
|
||||||
|
at repl:2:9
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Attaching Extra Info
|
||||||
|
|
||||||
|
Whether for helping with error recovery or just for debugging, it is often helpful to include some of the state that caused the error in the error. One way to do this is to put it in the message, but this has a few problems:
|
||||||
|
|
||||||
|
- Even if the error is later handled and recovered from, we spend time stringifying the state to add it to the error message.
|
||||||
|
- Error monitoring systems often look at the message when trying to group similar errors together, and they can get confused by the ever-changing messages.
|
||||||
|
- When using structured logging, you lose the ability to easily query or filter the logs based on the state; instead clever regexes may be required to get it out of the messages.
|
||||||
|
|
||||||
|
Instead, `OError`s (and subclasses) support an `info` object that can contain arbitrary data. Using `info`, we might write the above example as:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class UserNotFoundError extends OError {
|
||||||
|
constructor(userId) {
|
||||||
|
super('user not found', { userId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw new UserNotFoundError(123)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(OError.getFullStack(error))
|
||||||
|
console.error(OError.getFullInfo(error))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
UserNotFoundError: user not found
|
||||||
|
at repl:2:9
|
||||||
|
...
|
||||||
|
{ userId: 123 }
|
||||||
|
```
|
||||||
|
|
||||||
|
The `OError.getFullInfo` helper merges the `info` on custom errors and any info added with `OError.tag` on its way up the stack. It is intended for use when logging errors. If trying to recover from an error that is known to be a `UserNotFoundError`, it is usually better to interrogate `error.info.userId` directly.
|
||||||
|
|
||||||
|
### Wrapping an Internal Error
|
||||||
|
|
||||||
|
Detecting a condition like 'user not found' in the example above often starts with an internal database error. It is possible to just let the internal database error propagate all the way up through the stack, but this makes the code more coupled to the internals of the database (or database driver). It is often cleaner to handle and wrap the internal error in one that is under your control. Tying up the examples above:
|
||||||
|
|
||||||
|
```js
|
||||||
|
async function sayHi5(userId) {
|
||||||
|
try {
|
||||||
|
const user = await demoDatabase.findUserAsync(userId)
|
||||||
|
return `Hi ${user.name}`
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message === 'not found') {
|
||||||
|
throw new UserNotFoundError(userId).withCause(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
await sayHi5(43)
|
||||||
|
} catch (error) {
|
||||||
|
OError.tag(error, 'failed to say hi')
|
||||||
|
console.error(OError.getFullStack(error))
|
||||||
|
console.error(OError.getFullInfo(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
The output includes the wrapping error, the tag and the cause, together with the info:
|
||||||
|
|
||||||
|
```
|
||||||
|
UserNotFoundError: user not found
|
||||||
|
at sayHi5 (repl:7:13)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:68:7)
|
||||||
|
TaggedError: failed to say hi
|
||||||
|
at main (repl:5:12)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:68:7)
|
||||||
|
caused by:
|
||||||
|
Error: not found
|
||||||
|
at process.nextTick (repl:8:18)
|
||||||
|
at process._tickCallback (internal/process/next_tick.js:61:11)
|
||||||
|
{ userId: 43 }
|
||||||
|
```
|
||||||
|
|
||||||
|
## OError API Reference
|
||||||
|
|
||||||
|
<a name="OError"></a>
|
||||||
|
* [OError](#OError)
|
||||||
|
* [new OError(message, [info], [cause])](#new_OError_new)
|
||||||
|
* _instance_
|
||||||
|
* [.withInfo(info)](#OError+withInfo) ⇒ <code>this</code>
|
||||||
|
* [.withCause(cause)](#OError+withCause) ⇒ <code>this</code>
|
||||||
|
* _static_
|
||||||
|
* [.tag(error, [message], [info])](#OError.tag) ⇒ <code>Error</code>
|
||||||
|
* [.getFullInfo(error)](#OError.getFullInfo) ⇒ <code>Object</code>
|
||||||
|
* [.getFullStack(error)](#OError.getFullStack) ⇒ <code>string</code>
|
||||||
|
|
||||||
|
<a name="new_OError_new"></a>
|
||||||
|
|
||||||
|
### new OError(message, [info], [cause])
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| message | <code>string</code> | as for built-in Error |
|
||||||
|
| [info] | <code>Object</code> | extra data to attach to the error |
|
||||||
|
| [cause] | <code>Error</code> | the internal error that caused this error |
|
||||||
|
|
||||||
|
<a name="OError+withInfo"></a>
|
||||||
|
|
||||||
|
### oError.withInfo(info) ⇒ <code>this</code>
|
||||||
|
Set the extra info object for this error.
|
||||||
|
|
||||||
|
**Kind**: instance method of [<code>OError</code>](#OError)
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| info | <code>Object</code> \| <code>null</code> \| <code>undefined</code> | extra data to attach to the error |
|
||||||
|
|
||||||
|
<a name="OError+withCause"></a>
|
||||||
|
|
||||||
|
### oError.withCause(cause) ⇒ <code>this</code>
|
||||||
|
Wrap the given error, which caused this error.
|
||||||
|
|
||||||
|
**Kind**: instance method of [<code>OError</code>](#OError)
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| cause | <code>Error</code> | the internal error that caused this error |
|
||||||
|
|
||||||
|
<a name="OError.tag"></a>
|
||||||
|
|
||||||
|
### OError.tag(error, [message], [info]) ⇒ <code>Error</code>
|
||||||
|
Tag debugging information onto any error (whether an OError or not) and
|
||||||
|
return it.
|
||||||
|
|
||||||
|
**Kind**: static method of [<code>OError</code>](#OError)
|
||||||
|
**Returns**: <code>Error</code> - the modified `error` argument
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| error | <code>Error</code> | the error to tag |
|
||||||
|
| [message] | <code>string</code> | message with which to tag `error` |
|
||||||
|
| [info] | <code>Object</code> | extra data with wich to tag `error` |
|
||||||
|
|
||||||
|
<a name="OError.getFullInfo"></a>
|
||||||
|
|
||||||
|
### OError.getFullInfo(error) ⇒ <code>Object</code>
|
||||||
|
The merged info from any `tag`s on the given error.
|
||||||
|
|
||||||
|
If an info property is repeated, the last one wins.
|
||||||
|
|
||||||
|
**Kind**: static method of [<code>OError</code>](#OError)
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| error | <code>Error</code> \| <code>null</code> \| <code>undefined</code> | any errror (may or may not be an `OError`) |
|
||||||
|
|
||||||
|
<a name="OError.getFullStack"></a>
|
||||||
|
|
||||||
|
### OError.getFullStack(error) ⇒ <code>string</code>
|
||||||
|
Return the `stack` property from `error`, including the `stack`s for any
|
||||||
|
tagged errors added with `OError.tag` and for any `cause`s.
|
||||||
|
|
||||||
|
**Kind**: static method of [<code>OError</code>](#OError)
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| error | <code>Error</code> \| <code>null</code> \| <code>undefined</code> | any error (may or may not be an `OError`) |
|
||||||
|
<!-- END API REFERENCE -->
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- [MDN: Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
|
- [MDN: Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
|
||||||
|
|
141
libraries/o-error/doc/demo.js
Normal file
141
libraries/o-error/doc/demo.js
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
// This is the code from the README.
|
||||||
|
|
||||||
|
const demoDatabase = {
|
||||||
|
findUser(id, callback) {
|
||||||
|
process.nextTick(() => {
|
||||||
|
// return result asynchronously
|
||||||
|
if (id === 42) {
|
||||||
|
callback(null, { name: 'Bob' })
|
||||||
|
} else {
|
||||||
|
callback(new Error('not found'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function sayHi1(userId, callback) {
|
||||||
|
demoDatabase.findUser(userId, (err, user) => {
|
||||||
|
if (err) return callback(err)
|
||||||
|
callback(null, 'Hi ' + user.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sayHi1(42, (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err)
|
||||||
|
} else {
|
||||||
|
console.log(result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
sayHi1(43, (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err)
|
||||||
|
} else {
|
||||||
|
console.log(result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const OError = require('.')
|
||||||
|
|
||||||
|
function sayHi2(userId, callback) {
|
||||||
|
demoDatabase.findUser(userId, (err, user) => {
|
||||||
|
if (err) return callback(OError.tag(err))
|
||||||
|
callback(null, 'Hi ' + user.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sayHi2(43, (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(OError.getFullStack(OError.tag(err)))
|
||||||
|
} else {
|
||||||
|
console.log(result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function sayHi3(userId, callback) {
|
||||||
|
demoDatabase.findUser(userId, (err, user) => {
|
||||||
|
if (err) return callback(OError.tag(err, 'failed to find user', { userId }))
|
||||||
|
callback(null, 'Hi ' + user.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sayHi3(43, (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
OError.tag(err, 'failed to say hi')
|
||||||
|
console.error(OError.getFullStack(err))
|
||||||
|
console.error(OError.getFullInfo(err))
|
||||||
|
} else {
|
||||||
|
console.log(result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const promisify = require('util').promisify
|
||||||
|
demoDatabase.findUserAsync = promisify(demoDatabase.findUser)
|
||||||
|
|
||||||
|
async function sayHi4NoHandling(userId) {
|
||||||
|
const user = await demoDatabase.findUserAsync(userId)
|
||||||
|
return `Hi ${user.name}`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sayHi4(userId) {
|
||||||
|
try {
|
||||||
|
const user = await demoDatabase.findUserAsync(userId)
|
||||||
|
return `Hi ${user.name}`
|
||||||
|
} catch (error) {
|
||||||
|
throw OError.tag(error, 'failed to find user', { userId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
await sayHi4NoHandling(43)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(OError.getFullStack(error))
|
||||||
|
console.error(OError.getFullInfo(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sayHi4(43)
|
||||||
|
} catch (error) {
|
||||||
|
OError.tag(error, 'failed to say hi')
|
||||||
|
console.error(OError.getFullStack(error))
|
||||||
|
console.error(OError.getFullInfo(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main()
|
||||||
|
|
||||||
|
class UserNotFoundError extends OError {
|
||||||
|
constructor(userId) {
|
||||||
|
super('user not found', { userId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw new UserNotFoundError(123)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(OError.getFullStack(error))
|
||||||
|
console.error(OError.getFullInfo(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sayHi5(userId) {
|
||||||
|
try {
|
||||||
|
const user = await demoDatabase.findUserAsync(userId)
|
||||||
|
return `Hi ${user.name}`
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message === 'not found') {
|
||||||
|
throw new UserNotFoundError(userId).withCause(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main2() {
|
||||||
|
try {
|
||||||
|
await sayHi5(43)
|
||||||
|
} catch (error) {
|
||||||
|
OError.tag(error, 'failed to say hi')
|
||||||
|
console.error(OError.getFullStack(error))
|
||||||
|
console.error(OError.getFullInfo(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main2()
|
49
libraries/o-error/doc/update-readme.js
Executable file
49
libraries/o-error/doc/update-readme.js
Executable file
|
@ -0,0 +1,49 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const jsdoc2md = require('jsdoc-to-markdown')
|
||||||
|
const toc = require('markdown-toc')
|
||||||
|
|
||||||
|
const README = 'README.md'
|
||||||
|
const HEADER = '## OError API Reference'
|
||||||
|
const FOOTER = '<!-- END API REFERENCE -->'
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const apiDocs = await jsdoc2md.render({ files: 'index.js' })
|
||||||
|
const apiDocLines = apiDocs.trim().split(/\r?\n/g)
|
||||||
|
|
||||||
|
// The first few lines don't make much sense when included in the README.
|
||||||
|
const apiDocStart = apiDocLines.indexOf('* [OError](#OError)')
|
||||||
|
if (apiDocStart === -1) {
|
||||||
|
console.error('API docs not in expected format for insertion.')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
apiDocLines.splice(1, apiDocStart - 1)
|
||||||
|
apiDocLines.unshift(HEADER, '')
|
||||||
|
|
||||||
|
const readme = await fs.promises.readFile(README, { encoding: 'utf8' })
|
||||||
|
const readmeLines = readme.split(/\r?\n/g)
|
||||||
|
|
||||||
|
const apiStart = readmeLines.indexOf(HEADER)
|
||||||
|
const apiEnd = readmeLines.indexOf(FOOTER)
|
||||||
|
|
||||||
|
if (apiStart === -1 || apiEnd === -1) {
|
||||||
|
console.error('Could not find the API Reference section.')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.prototype.splice.apply(
|
||||||
|
readmeLines,
|
||||||
|
[apiStart, apiEnd - apiStart].concat(apiDocLines)
|
||||||
|
)
|
||||||
|
|
||||||
|
const readmeWithApi = readmeLines.join('\n')
|
||||||
|
|
||||||
|
let readmeWithApiAndToc = toc.insert(readmeWithApi)
|
||||||
|
|
||||||
|
// Unfortunately, the ⇒ breaks the generated TOC links.
|
||||||
|
readmeWithApiAndToc = readmeWithApiAndToc.replace(/-%E2%87%92-/g, '--')
|
||||||
|
|
||||||
|
await fs.promises.writeFile(README, readmeWithApiAndToc)
|
||||||
|
}
|
||||||
|
main()
|
|
@ -1,12 +1,12 @@
|
||||||
/**
|
/**
|
||||||
* Light-weight helpers for handling JavaScript Errors in node.js and the
|
* Light-weight helpers for handling JavaScript Errors in node.js and the
|
||||||
* browser. {@see README.md}
|
* browser.
|
||||||
*/
|
*/
|
||||||
class OError extends Error {
|
class OError extends Error {
|
||||||
/**
|
/**
|
||||||
* @param {string} message as for built-in Error
|
* @param {string} message as for built-in Error
|
||||||
* @param {Object} [info] extra data to attach to the error
|
* @param {Object} [info] extra data to attach to the error
|
||||||
* @param {Error} [cause]
|
* @param {Error} [cause] the internal error that caused this error
|
||||||
*/
|
*/
|
||||||
constructor(message, info, cause) {
|
constructor(message, info, cause) {
|
||||||
super(message)
|
super(message)
|
||||||
|
@ -14,14 +14,14 @@ class OError extends Error {
|
||||||
if (info) this.info = info
|
if (info) this.info = info
|
||||||
if (cause) this.cause = cause
|
if (cause) this.cause = cause
|
||||||
|
|
||||||
/** @type {Array<TaggedError>} */
|
/** @private @type {Array<TaggedError>} */
|
||||||
this._oErrorTags // eslint-disable-line
|
this._oErrorTags // eslint-disable-line
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the extra info object for this error.
|
* Set the extra info object for this error.
|
||||||
*
|
*
|
||||||
* @param {Object | null | undefined} info
|
* @param {Object | null | undefined} info extra data to attach to the error
|
||||||
* @return {this}
|
* @return {this}
|
||||||
*/
|
*/
|
||||||
withInfo(info) {
|
withInfo(info) {
|
||||||
|
@ -32,7 +32,7 @@ class OError extends Error {
|
||||||
/**
|
/**
|
||||||
* Wrap the given error, which caused this error.
|
* Wrap the given error, which caused this error.
|
||||||
*
|
*
|
||||||
* @param {Error} cause
|
* @param {Error} cause the internal error that caused this error
|
||||||
* @return {this}
|
* @return {this}
|
||||||
*/
|
*/
|
||||||
withCause(cause) {
|
withCause(cause) {
|
||||||
|
@ -44,9 +44,9 @@ class OError extends Error {
|
||||||
* Tag debugging information onto any error (whether an OError or not) and
|
* Tag debugging information onto any error (whether an OError or not) and
|
||||||
* return it.
|
* return it.
|
||||||
*
|
*
|
||||||
* @param {Error} error
|
* @param {Error} error the error to tag
|
||||||
* @param {string} [message]
|
* @param {string} [message] message with which to tag `error`
|
||||||
* @param {Object} [info]
|
* @param {Object} [info] extra data with wich to tag `error`
|
||||||
* @return {Error} the modified `error` argument
|
* @return {Error} the modified `error` argument
|
||||||
*/
|
*/
|
||||||
static tag(error, message, info) {
|
static tag(error, message, info) {
|
||||||
|
@ -69,7 +69,7 @@ class OError extends Error {
|
||||||
*
|
*
|
||||||
* If an info property is repeated, the last one wins.
|
* If an info property is repeated, the last one wins.
|
||||||
*
|
*
|
||||||
* @param {Error} error
|
* @param {Error | null | undefined} error any errror (may or may not be an `OError`)
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
static getFullInfo(error) {
|
static getFullInfo(error) {
|
||||||
|
@ -94,7 +94,7 @@ class OError extends Error {
|
||||||
* Return the `stack` property from `error`, including the `stack`s for any
|
* Return the `stack` property from `error`, including the `stack`s for any
|
||||||
* tagged errors added with `OError.tag` and for any `cause`s.
|
* tagged errors added with `OError.tag` and for any `cause`s.
|
||||||
*
|
*
|
||||||
* @param {Error | null | undefined} error
|
* @param {Error | null | undefined} error any error (may or may not be an `OError`)
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
static getFullStack(error) {
|
static getFullStack(error) {
|
||||||
|
|
1205
libraries/o-error/package-lock.json
generated
1205
libraries/o-error/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -5,6 +5,7 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
|
"update-readme": "doc/update-readme.js",
|
||||||
"test": "mocha",
|
"test": "mocha",
|
||||||
"typecheck": "tsc --allowJs --checkJs --noEmit --moduleResolution node --target ES6 *.js test/**/*.js"
|
"typecheck": "tsc --allowJs --checkJs --noEmit --moduleResolution node --target ES6 *.js test/**/*.js"
|
||||||
},
|
},
|
||||||
|
@ -25,6 +26,8 @@
|
||||||
"eslint-plugin-prettier": "^3.1.3",
|
"eslint-plugin-prettier": "^3.1.3",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"eslint-plugin-standard": "^4.0.1",
|
"eslint-plugin-standard": "^4.0.1",
|
||||||
|
"jsdoc-to-markdown": "^5.0.3",
|
||||||
|
"markdown-toc": "^1.2.0",
|
||||||
"mocha": "^7.1.1",
|
"mocha": "^7.1.1",
|
||||||
"prettier": "^2.0.2",
|
"prettier": "^2.0.2",
|
||||||
"typescript": "^3.8.3"
|
"typescript": "^3.8.3"
|
||||||
|
|
Loading…
Reference in a new issue