Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/xan105/node-error
Error handling tools
https://github.com/xan105/node-error
attempt error errorcode errorlookup fail failure match nodejs
Last synced: 8 days ago
JSON representation
Error handling tools
- Host: GitHub
- URL: https://github.com/xan105/node-error
- Owner: xan105
- License: mit
- Created: 2021-12-20T23:09:59.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2024-04-09T03:45:25.000Z (7 months ago)
- Last Synced: 2024-11-05T18:25:28.161Z (13 days ago)
- Topics: attempt, error, errorcode, errorlookup, fail, failure, match, nodejs
- Language: JavaScript
- Homepage:
- Size: 766 KB
- Stars: 1
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
About
=====Error handling tools:
- Custom Error type `Failure` extending the `Error` constructor
- Linux/Windows standard error codes and their description
- Error lookup: retrieve description associated to status code (or other code)
- GoLang style error handling: return value instead of throwing
- Rust style error handling: `match Result` pattern📦 Scoped `@xan105` packages are for my own personal use but feel free to use them.
Example
=======```js
import { Failure } from "@xan105/error";if (something)
throw new Failure("my super error message", "ERR_CODE");
```Output:
```
Failure [ERR_CODE]: my super error message
StackTrace...
.............
............. {
code: 'ERR_CODE'
}
```- GoLang style error
```js
import { attempt } from "@xan105/error";
import { readFile } from "node:fs/promises";const [ file, err ] = await attempt(readFile, [filePath]);
if(err) console.error(err); //handle error
//ignore error and set a default value
const [ json = {} ] = attempt(JSON.parse, [file]);
//skip value
const [, err] = attempt(foo, ["bar"]);//if you prefer a node:util/promisify like syntax
import { attemptify } from "@xan105/error";
const [json] = attemptify(JSON.parse)(file);
```- Rust style error (`match Result` pattern)
```js
import { match } from "@xan105/error";
import { readFile } from "node:fs/promises";const file = await match(readFile, [filePath], {
Err: (err) => { console.error(err); } //handle error
});
```- Windows error lookup with shell32 API (FFI)
```js
import { Failure, errorLookup } from "@xan105/error";// ... Some FFI implementation code
const hr = SHQueryUserNotificationState(pquns);
if (hr < 0) throw new Failure(...errorLookup(hr));
```Let's say this would fail with error `0x8000FFFF`
Output:
```
Failure [E_UNEXPECTED]: Catastrophic failure
StackTrace...
.............
............. {
code: 'E_UNEXPECTED'
}
```Install
=======```
npm install @xan105/error
```API
===⚠️ This module is only available as an ECMAScript module (ESM).
## Named export
### `Failure(message: string | object, option?: string | number | object): class`
Create an error with optional information.
This extends the regular `Error` constructor.|option|default|description|
|------|-------|-----------|
|code|none|optional custom error code (see below for details)|
|cause|none|parent error if any|
|clean|true|remove unhelpful internal stack trace entries|
|filter|none|additional string[] of path(s) to filter when using clean|
|info|none|an additional object/array/string to give more details about the error|`code` (if any) is expected to be a string if it's an integer then the following will be used instead:
0. ERR_UNEXPECTED
1. ERR_INVALID_ARG
2. ERR_ASSERTION
3. ERR_UNSUPPORTED
if `option` is either a string or a number then it specifies the error code.
Output Example:`new Failure("Expecting a string !","ERR_INVALID_ARG");`
```
Failure [ERR_INVALID_ARG]: Expecting a string !
at file:///D:/Documents/GitHub/xan105/node-error/test/test.js:3:12
at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
at async loadESM (node:internal/process/esm_loader:88:5)
at async handleMainPromise (node:internal/modules/run_main:65:12) {
code: 'ERR_INVALID_ARG'
}
````new Failure("Expecting a string !", { code: 1, info: { foo: "bar" } });`
```
Failure [ERR_INVALID_ARG]: Expecting a string !
at file:///D:/Documents/GitHub/xan105/node-error/test/test.js:3:12
at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
at async loadESM (node:internal/process/esm_loader:88:5)
at async handleMainPromise (node:internal/modules/run_main:65:12) {
code: 'ERR_INVALID_ARG',
info: { foo: 'bar' }
}
````new Failure("Expecting a string !", { clean: true });`
```
Failure: Expecting a string !
at file:///D:/Documents/GitHub/xan105/node-error/test/test.js:3:12
at async Promise.all (index 0)
```### `codes: object`
A list of standard error codes with their description.
Errors are listed by their unsigned numerical value as `value:number = [description: string, code: string]````js
{
1: ["Operation not permitted", "EPERM"],
2: ["No such file or directory", "ENOENT"],
3: ["No such process", "ESRCH"],
...
}
```#### Available error code range are:
- `linux`
Linux error codes 1 to 131.
- `windows`
Windows error codes 1 to 15841.
- `hresult`
HRESULT codes are most commonly encountered in COM programming. Includes common and WMI error codes.
- `ntstatus`
NTSTATUS values are mostly used like HRESULT but they have different codes.
- `win32`
Windows and hresult error codes merged together since their error code range don't overlap.
This is also for backward compatibility with previous version of `errorLookup()`#### They are also available under their own namespace:
```js
import { codes } from "@xan105/error"
console.log(codes.windows);import { windows } from "@xan105/error/codes"
console.log(windows);
```#### Usage example:
Linux
```js
import { Failure, codes } from "@xan105/error"
throw new Failure(...codes.linux[2]);
/*
Failure [ENOENT]: No such file or directory
StackTrace...
.............
............. {
code: 'ENOENT'
}
*/
``````js
import { Failure, codes } from "@xan105/error"
const [description, code] = codes.linux[1];
throw new Failure(description, { code, info: { foo: "bar" } });
/*
Failure [EPERM]: Operation not permitted,
StackTrace...
.............
............. {
code: 'EPERM',
info: { foo: 'bar' }
}
*/
```Windows
```js
import { Failure, codes } from "@xan105/error"
throw new Failure(...codes.windows[2]);
/*
Failure [ERROR_FILE_NOT_FOUND]: The system cannot find the file specified
StackTrace...
.............
............. {
code: 'ERROR_FILE_NOT_FOUND'
}
*/
``````js
import { Failure, codes } from "@xan105/error"
const [description, code] = codes.windows[1];
throw new Failure(description, { code, info: { foo: "bar" } });
/*
Failure [ERROR_INVALID_FUNCTION]: Incorrect function
StackTrace...
.............
............. {
code: 'ERROR_INVALID_FUNCTION',
info: { foo: 'bar' }
}
*/
```Windows HRESULT
Example with error `2147749921 (0x80041021)`:
```js
import { Failure, codes } from "@xan105/error"const hr = someWin32API(); //received error -2147217375
const code = new Uint32Array([hr])[0]; //cast signed to unsigned
throw new Failure(...codes.hresult[code]);
/*
Failure [WBEM_E_INVALID_SYNTAX]: Query is syntactically not valid
StackTrace...
.............
............. {
code: 'WBEM_E_INVALID_SYNTAX'
}
*/
```### `errorLookup(code: number | string, range?: string): string[]`
Retrieve information about an error by its numerical status code (or other code).
Return an array of string as `[message: string, code?: string]`.
You can use it directly with `Failure`:
```js
new Failure(...errorLookup(0x80041021));
new Failure(...errorLookup(2147749921));
new Failure(...errorLookup(-2147217375));
new Failure(...errorLookup("WBEM_E_INVALID_SYNTAX"));/*
Failure [WBEM_E_INVALID_SYNTAX]: Query is syntactically not valid
StackTrace...
.............
............. {
code: 'WBEM_E_INVALID_SYNTAX'
}
*/
```See `codes` above for available error code range.
If omitted `linux` is used under Linux and `win32` under Windows.### `attempt(fn: unknown, args?: unknown[]):Promise | unknown[]`
This is a try/catch wrapper to change how an error is handled.
Instead of throwing returns an error as a value similar to GoLang.By leveraging the destructure syntax we can easily provide a default value in case of error and/or choose to completely ignore to handle any error.
And if we want to handle any error we can do so like we would with any value.Example
```js
import { readFile } from "node:fs/promises";
//read the file
const [ file, err ] = await attempt(readFile, [filePath]);
if(err) //in case of error do something;//ignore error and set a default value
const [ json = {} ] = attempt(JSON.parse, [file]);//skip value
const [, err] = attempt(foo, ["bar"]);
```This doesn't replace try/catch it's an alternative.
It is particularly useful to avoid these patterns:- Using `let` instead of `const` because the variable needs to be outside of the try/catch scope.
```js
//Instead of
let json;
try{
json = JSON.parse(string);
} catch { /*do nothing*/ }
return json; //do something//You could do
const [ json ] = attempt(JSON.parse,[string]);
return json; //do something
```- Nested try/catch which are sometimes unavoidable and impact readability.
```js
//Instead of
try{
foo();
}catch(err){
try{
bar();
}catch(err){
if (err.code === "ENOENT")
throw new Error("It didn't work");
}
}//You could do
if (attempt(foo)[1] && attempt(bar)[1]?.code === "ENOENT"){
throw new Error("It didn't work");
}
```**Parameters:**
- fn: The value to resolve
If `fn` is a promise then this function will behave like one.
eg:
```js
//Promise
const [ file ] = await attempt(fs.Promises.readFile, [filePath]);
//Sync
const [ json ] = attempt(JSON.parse, [file]);
```You can also use anonymous function (or wrap in one).
eg:
```js
const [result] = attempt( ()=> "value" );
const [result] = attempt( (x)=> x, ["value"] );
const [json] = attempt(()=> JSON.parse(string) );
const [json] = attempt(()=> JSON.stringify(JSON.parse(string)) );
```
- args: Optional list of arguments to pass to fn**Return value:**
Returns the result and the error together as an array as `[result, error]`.
If there is an error result will be _undefined_.
Otherwise error will be _undefined_.
💡 _undefined_ is used to represent the lack/nonexistence of value because destructuring default value assignment triggers only with _undefined_.Usage example with node:util/promisify:
```js
import { promisify } from "node:util";
import { execFile } from "node:child_process";
import { attempt, Failure } from "@xan105/error";const [ ps, err ] = await attempt(promisify(execFile), ["pwsh", [
"-NoProfile",
"-NoLogo",
"-Command",
"$PSVersionTable.PSVersion.ToString()"
], { windowsHide: true }]);if (err || ps.stderr) throw new Failure(err?.stderr || ps.stderr, "ERR_POWERSHELL");
console.log(ps.stdout);
```#### Special case
##### Loosing "this" context
⚠️ NB: If you get an error like:
```
TypeError: x called on non-object
TypeError: Illegal invocation
```This is most likely because what you are invoking lost its `this` context.
You need to `bind` it to its constructor or use an arrow function.`Promise` static methods such as `.all()`, `.any()`, `.allSettled()` , etc is a good example of this:
```js
const promise1 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));
const promises = [promise1, promise2];//This will succeed
const [ result, error ] = await attempt(()=> Promise.any(promises));//This will fail with a TypeError
const [ result, error ] = await attempt(Promise.any, [promises]);
/*
[
undefined,
TypeError: Promise.any called on non-object
at any ()
StackTrace...
]
*///This will succeed
const [ result, error ] = await attempt(Promise.any.bind(Promise), [promises]);
```##### Computed properties (Getter/Setter)
The actual function behind the Getter/Setter will be called when that property is looked up.
Therefore if there was any error it would throw before `attempt()` could catch it.```js
class Foo {
constructor(){}
get bar(){
throw new Error("error");
}
}const foo = new Foo();
const [, err] = attempt(foo.bar);
```To catch it you need to pass the actual function behind the Getter/Setter.
```js
class Foo {
constructor(){}
get bar(){
throw new Error("error");
}
}const foo = new Foo();
const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(foo), "bar");
const [, err] = attempt(descriptor?.["get"]);
```### `attemptify(fn: unknown): (...args: unknown[]) => Promise | unknown[]`
node:util/promisify style syntax for `attempt()`:
```js
function double(i){
return i * 2;
}//Instead of
const j = attempt(double, [2]);//You can use
const j = attemptify(double)(2);
```This is a simple wrapper to `attempt()`.
### `match(fn: unknown, args: unknown[], cb?: { Ok?: function, Err?: function }):Promise | unknown`
This is a try/catch wrapper to change how an error is handled.
Similar to the Rust `match Result` pattern:Success is handled by the `Ok()` function and failure by the `Err()` function respectively.
```js
const greetings = (name) => `hello ${name}`;const message = match(greetings, ["Xan"], {
Ok: (value) => value,
Err: (err) => { console.log(err) }
});
```The `Ok()` function is expected to return the value the `match()` function shall return for value assignment.
💡 `Ok()` and `Err()` can both be omitted.```js
const greetings = (name) => `hello ${name}`;const message = match(greetings, ["Xan"], {
Err: (err) => { console.log(err) }
});
```If `fn` is a promise then this function will behave like one:
```js
//Promise
const file = await match(fs.Promises.readFile, [filePath]);
//Sync
const json = match(JSON.parse, [file]);
```In case of error, If `Err()` returns a value it will be used for value assignment.
Use this to ignore an error and set a default value:```js
const json = match(JSON.parse, [file], {
Err: ()=> { return {} }
});
```#### Special case
⚠️ This function is similar to the above export `attempt()` and therefore inherits the same remarks (_see above_).