@locustjs/exception
v2.3.0
Published
This library provides Exception classes and utilities regarding errors in javascript programs.
Readme
@locustjs/exception
This library provides a base Exception class and a TryCatch utlity class with three Try(), Catch() and Finally() functions.
Current Version
2.2.0Installation
NPM
npm i @locustjs/exceptionYarn
yarn add @locustjs/exceptionException class
Exception class provides a better and more meaningful error compared to javascript errors. It is a base class from which all business exceptions could be derived.
constructor
Exception(settings)parameters:
settings: anExceptioninstance, javascript Error object or a custom object in the shape of such errors.
Properties
|Property|Type|Description |example |default| |--|--|--|--|--| | name |string|exception name| ArgumentNullException |exception class name| | baseName |string| Error object from which the exception is created| | undefined| | message |string| exception or error message|argument 'a' is null or undefined |empty string| | code |number| error code|200500 |undefined| | status |string| status code|argument-is-null |empty string| | host |string| Hosting application from which the exception is raised | Chrome58.0|undefined| | date |Date| date/time the exception is raised |2020-09-15T13:24:42|new Date()| | data |object |custom data included with the exception|{ fn: 'John', ln: 'Doe'}|null| | stack | string |function call stack at the time the exception was raised||Error.captureStackTrace()| | stackTrace | StackTrace |a helper StackTrace object that simplifies iterating in the stack info||null| | innerException |Exception|an exception by which current exception is raised | |null| | fileName |string|name of the javascript source file in which the exception was raised | myapp.js|undefined| | lineNumber |number |line number in the javascript source file in which the exception was raised|12|undefined| | columnNumber |number |column number in the javascript source file in which the exception was raised|5|undefined|
Methods
toString(separator): converts the exception including its inner exception(s) hierarchy to a string separated by given separator (default = \n).flatten(): converts the hierarchy of inner exceptions to a flattened array.
example 1:
const ex = new Exception({
message: 'calling web api failed',
code: 100324,
status: 'call-api-failed',
data: { url: '/api/v1/product', method: 'POST' data: { id: 1, title: 'Product II' } }
});example 2:
try {
var f;
console.log(f.test())
} catch (e) {
const ex = new Exception(e);
console.log(JSON.stringify(e));
}
/*
{
"name": "Exception",
"baseName": "TypeError",
"message": "Cannot read property 'test' of undefined",
"stack": "TypeError: Cannot read properties of undefined (reading 'test')
at foo (file:///C:/path/to/my/app/test.html:46:7)
at HTMLButtonElement.<anonymous> (file:///C:/path/to/my/app/test.html:51:7)
at HTMLButtonElement.dispatch (https://code.jquery.com/jquery-1.8.3.min.js:2:38053)
at HTMLButtonElement.u (https://code.jquery.com/jquery-1.8.3.min.js:2:33916)",
"stackTrace": {
"items": [
{
"line": 46,
"col": 7,
"callSite": "foo",
"source": "file:///C:/path/to/my/app/test.html",
"message": " at foo (file:///C:/path/to/my/app/test.html:46:7)"
},
{
"line": 51,
"col": 7,
"callSite": "HTMLButtonElement.<anonymous>",
"source": "file:///C:/path/to/my/app/test.html",
"message": " at HTMLButtonElement.<anonymous> (file:///C:/path/to/my/app/test.html:51:7)"
},
{
"line": 2,
"col": 38053,
"callSite": "HTMLButtonElement.dispatch",
"source": "https://code.jquery.com/jquery-1.8.3.min.js",
"message": " at HTMLButtonElement.dispatch (https://code.jquery.com/jquery-1.8.3.min.js:2:38053)"
},
{
"line": 2,
"col": 33916,
"callSite": "HTMLButtonElement.u",
"source": "https://code.jquery.com/jquery-1.8.3.min.js",
"message": " at HTMLButtonElement.u (https://code.jquery.com/jquery-1.8.3.min.js:2:33916)\""
}
]
},
"innerException": null,
"data": null,
"date": "2022-10-16T11:42:10.223Z"
}
*/Derived classes
PropertyReadOnlyExceptionAbstractInstantiationExceptionNotImplementedExceptionNotSupportedExceptionIndexOutOfRangeExceptionValueNotBetweenExceptionValueIsBetweenExceptionArgumentNullExceptionArgumentUndefinedExceptionArgumentNullOrUndefinedExceptionArgumentNullOrEmptyExceptionArgumentEmptyExceptionArgumentNothingExceptionArgumentTypeIncorrectExceptionPropertyMissingExceptionComparisonFailedExceptionNotInstanceOfExceptionInvalidValueExceptionInvalidHttpMethodException
StackTrace
Processes a stack-trace value and populates a list of StackTraceItem objects to facilitate working with a stack-trace information.
By default,
Exceptionclass processesstackTraceof a javascriptErrorobject into aStackTraceinstance.
constructor
StackTrace(stackTrace: string)parameters:
stackTrace: strack trace of a javascript error object
Properties
|Property|Type|Description|
|--|--|--|
| items |StackTraceItem| List of stack-trace items|
example:
const st = new StackTrace(`"TypeError: Cannot read properties of undefined (reading 'test')
at foo (file:///C:/path/to/my/app/test.html:46:7)
at HTMLButtonElement.<anonymous> (file:///C:/path/to/my/app/test.html:51:7)
at HTMLButtonElement.dispatch (https://code.jquery.com/jquery-1.8.3.min.js:2:38053)
at HTMLButtonElement.u (https://code.jquery.com/jquery-1.8.3.min.js:2:33916)"`);
console.log(JSON.stringify(st, null, ' '));
/*
{
"items": [
{
"line": 46,
"col": 7,
"callSite": "foo",
"source": "file:///C:/path/to/my/app/test.html",
"message": " at foo (file:///C:/path/to/my/app/test.html:46:7)"
},
{
"line": 51,
"col": 7,
"callSite": "HTMLButtonElement.<anonymous>",
"source": "file:///C:/path/to/my/app/test.html",
"message": " at HTMLButtonElement.<anonymous> (file:///C:/path/to/my/app/test.html:51:7)"
},
{
"line": 2,
"col": 38053,
"callSite": "HTMLButtonElement.dispatch",
"source": "https://code.jquery.com/jquery-1.8.3.min.js",
"message": " at HTMLButtonElement.dispatch (https://code.jquery.com/jquery-1.8.3.min.js:2:38053)"
},
{
"line": 2,
"col": 33916,
"callSite": "HTMLButtonElement.u",
"source": "https://code.jquery.com/jquery-1.8.3.min.js",
"message": " at HTMLButtonElement.u (https://code.jquery.com/jquery-1.8.3.min.js:2:33916)\""
}
]
}
*/StackTraceItem
Processes one line of a stack-trace information and presents it as an object by extracting its details.
constructor
StackTraceItem(line: string)parameters:
line: one line of a stack-trace information
Properties
|Property|Type|Description|
|--|--|--|
| line |number| line number of error |
| col |number| column number of error |
| callSite |string| function name at which the error was raised |
| source |string| file name the error was raised at |
| message |string| given error info |
example:
let sti;
sti = new StackTraceItem(`" at foo (file:///C:/path/to/my/app/test.html:46:7)"`);
console.log(JSON.stringify(sti, null, ' '));
/*
{
line: 46,
col: 7,
callSite: "foo",
source: "file:///C:/path/to/my/app/test.html"
}
*/
sti = new StackTraceItem(`" at <anonymous>:1:10"`);
console.log(JSON.stringify(sti, null, ' '));
/*
{
line: 1,
col: 10,
callSite: "<anonymous>",
source: ""
}
*/Utility functions
throwIfInstantiateAbstract(classType, instance, host)
Checks whether classType is an abstract class and if so, throws a new AbstractInstantiationException.
class MyAbstractClass {
constructor() {
throwIfInstantiateAbstract(MyAbstractClass, this)
}
...
}
class MyConcreteClass extends MyAbstractClass {
...
}
let obj = new MyAbstractClass(); // throws AbstractInstantiationExceptionthrowIfNotInstanceOf(argName, classType, instance, ignoreNull = false, host = '')
Checks whether given object is an instance of the specified classType. If not, it throws a new NotInstanceOfException.
class FooBase {
get Code() {
return this._code;
}
set Code(value) {
this._code = value;
}
...
}
class Foo extends FooBase {
...
}
function validate(f) {
throwIfNotInstanceOf(f, FooBase); // make sure f is an instance of FooBase
// f is a FooBase object. so, it definitely has a 'Code' prop.
if (f.Code.length < 3) {
...
}
}throwNotImplementedException(method, host)This method is mainly used in abstract classes and throws a newNotImplementedExceptionexception to show that child classes have to implement the method.
class FooBase {
doSomething() {
throwNotImplementedException(`${this.constructor.name}.doSomething`)
}
...
}throwIfNull(arg, argName, host)Checksargand throws aArgumentNullExceptionexception ifargisnull.
class FooBase {
doSomething(foo) {
throwIfNull(foo, 'foo')
...
}
...
}throwIfUndefined(arg, argName, host)Checksargand throws aArgumentUndefinedExceptionexception ifargisundefined.
class FooBase {
doSomething(foo) {
throwIfUndefined(foo, 'foo')
...
}
...
}throwIfNullOrUndefined(arg, argName, host)Checksargand throws aArgumentNullOrUndefinedExceptionexception ifargisnullorundefined.
class FooBase {
doSomething(foo) {
throwIfNullOrUndefined(foo, 'foo')
...
}
...
}throwIfNullOrEmpty(arg, argName, host)Checksargand throws aArgumentNullOrEmptyExceptionexception ifargisnull,undefinedor zero-length string.
class FooBase {
doSomething(foo) {
throwIfNullOrEmpty(foo, 'foo')
...
}
...
}throwIfEmptyChecksargand throws aArgumentEmptyExceptionexception ifargis empty (null,undefinedor empty strings).
class FooBase {
doSomething(foo) {
throwIfEmpty(foo, 'foo')
...
}
...
}throwIfNothingChecksargand throws aArgumentNothingExceptionexception ifargis empty (null,undefined, empty string or emty object).
class FooBase {
doSomething(foo) {
throwIfNothing(foo, 'foo')
...
}
...
}throwIfTypeIncorrect(arg, argName, typeOrCheckType, host)Checksargand throws aArgumentTypeIncorrectExceptionexception ifarg's type does not matchtypeOrCheckType'.
typeOrCheckType could be a string specifying the type to be checked or a custom function that checks type matching.
Intrinsic supported types:
number: numbernumber+: non-zero numbernumeric: number or a string containing a numberintorinteger: integerint+orinteger+: a non-zero integerfloat: floatfloat+: non-zero floatstring: stringstring+: non-zero length stringbool: boolbool*: bool or a non-trimmed string containingtrue,false,True,False,TRUE,FALSEbool#: bool or a string containingtrue,false,True,False,TRUE,FALSEbool^: bool or a string containingtrue,false,True,Falsebool!: bool or a string containingtrue,falsearray: arrayarray+: non-zero length arrayobject: objectobject+: non-empty objectdate: datefunction: functionbasic:number,string,bool,dateprimitive: basic data type or instances ofNumber,String,Boolean,Date- other: arg should be an instance of
class Foo { }
class Bar { }
...
function doSomething(a, b, c, d, e) {
throwIfTypeIncorrect(a, 'a', 'Foo')
throwIfTypeIncorrect(b, 'b', 'bool')
throwIfTypeIncorrect(c, 'c', 'number')
throwIfTypeIncorrect(d, 'd', 'string')
throwIfTypeIncorrect(e, 'e', x => x instanceof Bar)
...
}By default, throwIfTypeIncorrect requires the argument not to be null or undefined. Using the ? suffix, we can make the argument optional.
function doSomething(a, b) {
throwIfTypeIncorrect(a, 'a', 'number?')
throwIfTypeIncorrect(b, 'b', 'string+?')
...
}List of helpers created over throwIfTypeIncorrect:
throwIfNotNumberthrowIfNotSomeNumberthrowIfNotNumericthrowIfNotBoolthrowIfNotHasBoolthrowIfNotStringthrowIfNotSomeStringthrowIfNotDatethrowIfNotObjectthrowIfNotSomeObjectthrowIfNotFunctionthrowIfNotIntthrowIfNotSomeIntthrowIfNotFloatthrowIfNotSomeFloatthrowIfNotArraythrowIfNotSomeArraythrowIfNotBasicthrowIfNotPrimitivethrowIfNotInShape
All of the helpers, except throwIfNotInShape have the following signature:
fn(arg, argName, host)
example:
function sum(arr, fromIndex, toIndex) {
throwIfNotArray(arr, 'arr')
throwIfNotNumber(fromIndex, 'fromIndex')
throwIfNotNumber(toIndex, 'toIndex')
// we are safe here
...
}throwIfNotInShape signature is as follows:
throwIfNotInShape(arg, argName, shape, host)
example:
function validate(person) {
throwIfNotInShape(person, 'person', {
firstName: 'string+?', // not required
lastName: 'string+', // required
age: 'int?', // not required
phone: {
required: true,
validate: x => /^\d+$/.test(x)
},
location: {
shape: {
country: 'string',
city: 'string'
}
},
scores: {
array: true,
type: 'int',
validate: x => x >= 0 && x <= 100
}
})
// we are safe here
...
}throwIfIndexOutOfRange(index, min, max, host)Checksindexand throws aIndexOutOfRangeExceptionexception ifindexis not betweenmin(inclusive) andmax(exclusive).
class Foo { }
...
function concat(arr, from, to) {
throwIfIndexOutOfRange(from, `from`, 0, arr.length);
throwIfIndexOutOfRange(to, `to`, 0, arr.length);
...
}throwIfMissingProperty(obj, objName, prop, host)Checks whetherobjcontains a property namedpropand if not throws aPropertyMissingExceptionexception.
...
function login(credentials) {
throwIfMissingProperty(credentials, `credentials`, 'username');
throwIfMissingProperty(credentials, `credentials`, 'password');
...
}throwIfComparisonFailed(arg)Checksarg.aandarg.band throws aComparisonFailedExceptionexception if comparingarg.aandarg.bbased onarg.operatordoes not succeed.
Structure of arg:
{
a: 10,
b: 20,
operator: '<'
}Example:
...
function concat(arr, from, to) {
throwIfComparisonFailed({ a: from, b: to, operator: '>' });
...
}List of helpers created over throwIfComparisonFailed:
throwIfLessThan(a, b)throwIfLessThanOrEqualTo(a, b)throwIfGreaterThan(a, b)throwIfGreaterThanOrEqualTo(a, b)throwIfEqualTo(a, b)throwIfNotEqualTo(a, b)throwIfTypeEqualTo(a, b)throwIfNotTypeEqualTo(a, b)throwIfNotBetween(x, from , to)throwIfBetween(x, from , to)
Example:
...
function concat(arr, from, to) {
throwIfGreaterThan(from, to);
...
}Try/Catch/Finally
examples
Assume we have the following functions:
function f1(a) { return a.length }
function f2(a) { console.log(f1(a)) }The ordinary try/catch block is this way:
try {
f2();
} catch (e) {
console.log(e);
} finally {
console.log('Finished.')
}Here is various examples of using Try/Catch:
Example 1 (basic):
Try(_ => f2())
.Catch(e => console.log(e))
.Finally(_ => console.log('Finished.'))Try/Catch provides multiple catch ability, a feature javascript try/catch does not support.
Example 2:
function f1(a) {
if (a === null) {
throw new ArgumentNullException('a');
} else if (a === undefined) {
throw new ArgumentUndefinedException('a');
} else if (!isSomeString(a)) {
throw new ArgumentTypeIncorrectException('a', 'string');
}
return a.length
}
function f2(a) {
console.log(f1(a))
}
Try(f1)
.Catch(ArgumentNullException, console.log)
.Catch(ArgumentUndefinedException, console.log)
.Catch(ArgumentTypeIncorrectException, console.log)
.Catch(e => console.log(`Other exception raised: ${e}`)) // this Catch() is not triggered
.Finally(_ => console.log('Finished.'))Example 3: verbose
const x = Try(f2);
console.log('do something outside of previous Try');
x.Catch(e => console.log(e));
x.Finally(_ => console.log('Finished.'));
/* output:
TypeError: Cannot read property 'length' of undefined
at f2 (<anonymous>:1:69)
at f1 (<anonymous>:1:30)
at TryCatch._fn (<anonymous>:1:19)
at TryCatch.Run (<anonymous>:93:22)
at Try (<anonymous>:161:16)
at <anonymous>:1:9
at <anonymous>:3:42
*/Example 4: external catch (not possible using traditional try/catch)
function doSomething(a) {
return Try(_ => {
console.log(`name: ${a.name}`);
});
}
doSomething()
.Catch(e => console.log(e))
.Finally('Finished');