Painless Error
Easily create multiple types of Error and match them. You can create error in different type in a simple function call dynamically without defining a new class.
import {PainlessError} from './index.js'
const Perr = PainlessError
let n = Math.random()
try {
if (n < 0.5) throw new Perr(`too-small: n ${n} too small`)
else throw new Perr(`too-big: n ${n} too big`)
}
catch (err) {
if (err.is('too-small')) n *= 2
else if (err.is('too-big')) n /= 2
else throw err
}
api
new
new PainlessError(name, [message, [cause]])
new PainlessError(`${name}: ${message}`, [cause])
name should be invariant. message is for human reading. cause can be any js value.
is (match)
test if an error match a name.
const err = new PainlessError('test-error/test-match: a error to test match')
err.is('test-error') == true
err.is('test-error/test-match') == true
err.is('test-match') == false
err.is('a error to test match') == false
it would be similar to the inherit error pattern,
though it use the is
method.
class TestError extends Error {}
class TestMatchError extends TestError {}
let tmerr = new TestMatchError()
(tmerr instanceof TestError) == true
(tmerr instanceof TestMatchError) == true
match with callback
match will execute the function whose name match the err. if nothing match and default is defined, it is executed. the longest name is matched first, and the tag is match in order.
const ctx = err.match({
'test-error/test-match'(ctx) {
ctx.error == err
ctx.tag == 'test-other'
ctx.goReturn = true
return 'ret'
},
'test-other'(ctx) {
ctx.tag == 'test-other'
ctx.goReturn = true
},
default(ctx) {
ctx.tag == 'default'
}
})
match return a context object, which is pass to the executed callback too.
you can pass state to outside by setting its properties.
the matched callback's return value is stored in the ctx.result
after the match return.
context would be useful if you want to return the outer function in some callback.
ctx.result == 'ret'
if (ctx.goReturn) return ctx.result
you can throw inside the callback too, thought it will introduce some more call stack.
since if ... else if ...
is verbose, i write this method.
tag
you can tag a name with another name, and the error with this name will match the tag.
Perr.tagName(`${name}: ${tag1} ${tag2}`)
err = new Perr(`${name}: some message`)
err.is(tag1) == true
err.is(tag2) == true
it should be useful if you want some errors in different names could be catch with a single term.
Perr.tagName('file-miss: file-picker')
Perr.tagName('file-null: file-picker')
new Perr('file-miss').is('file-picker') == true
new Perr('file-null').is('file-picker') == true
wrap
wrap a object to a PainlessError, copy its {name, message}
,
and the object is store in the cause
porperty.
let erro = new Error('some message')
err = Perr.wrap(erro)
err.match({
Error() {
console.log(`this will match since ${erro.name == 'Error'}`)
err.name == erro.name
err.message == erro.message
err.cause == erro
}
})
return the original object if the object is already
a instance of PainlessError
, except the second argument is true.
Perr.wrap(err) == err
Perr.wrap(err, true) != err
unwrap
return the original error if err.cause
is a error,
or return null.
erro = err.unwrap()
erro == err.cause
install
install from tarball, npm, or just import the index.js.
naming convension
a name should be non-space characters. i like kebab case so i use it. you can use camel case anyway, but the name always match the whole string or at the slash separator.
todo
- wrap change the call stack
- allow no readme msg
- tag syntax
test-error/example-error|tag1|tag2: a test error with tag