@backpack/error-handling
🚩 ​
A package with various utilities to simplify error handling.
Installing ​
First, ensure you have set up Nexus as private registry needed to use Backpack.
Then, install the package using npm:
npm install @backpack/error-handling
Result<T, E>
​
A small utility type representing either a successful value or a failure. Inspired by many other programming languages that offer similar monads.
type Result<T, E> = Success<T> | Failure<E>;
This object contains:
- either a
value: T
withsuccess: true
andfailed: false
- or an
error: E
withsuccess: false
andfailed: true
Example usage, using the runCatching()
function:
import { runCatching } from "@backpack/error-handling/result";
const result = runCatching(() => mightFail(), MyCustomError);
if (result.failed) {
console.warn("Got custom error", result.error);
return;
}
console.log("Got value", result.value);
runCatching<T>(block: () => T): Result<T, unknown>
​
Runs the provided callback, catching any exceptions, and wrapping its result in a Result
object.
Optionally accepts one or more error classes, catching only exceptions that are instances of these classes:
const result = runCatching(
() => mightFail(),
MyCustomError,
MyOtherError,
);
if (result.failed) {
const error = result.error;
// ^ inferred as `MyCustomError | MyOtherError`
}
Additionally, there is also an async version of this utility called runCatchingAsync()
, returning a Promise<Result>
.
success<T>(value: T): Success<T>
​
Creates a new result, representing a successful value.
failure(error: E): Failure<E>
​
Creates a new result, representing a failure with its associated error.
AsyncResult<T>
​
An extension of Promise<T>
, adding more operators to it, to state your intentions more explicitly.
Example:
import { AsyncResult } from "@backpack/error-handling/async-result";
const value = await AsyncResult.try(() => mightFail())
.map((r) => r * 2)
.onFailure((e) => console.warn("Got error", e))
.recoverIfInstanceOf(MyCustomError, () => "fallback-1")
.orElse("fallback-2");
console.log("Got value", value);
AsyncResult.try<T>(block: () => T): AsyncResult<T>
​
Executes a callback, wrapping its result in an AsyncResult<T>
.
Similar to Promise.try(), can be used to catch all errors in any regular function in a Promise-like style.
.map((v: T) => R)
​
Alias for .then()
, added to improve semantics.
.onSuccess((v: T) => void)
​
Executes the given callback if successful, letting the original value pass through.
.onFailure((e: unknown) => void)
​
Executes the given callback if failed, letting the original error pass through.
.onFailureIfInstanceOf(errorClass, (e: E) => void)
​
Executes the given callback if failed, if the error is an instance of the class provided, letting the original error pass through.
.recover(recoverFn)
​
If failed, maps the error to another value, to recover into a successful AsyncResult
.
.recoverIf(predicate, recoverFn)
​
If failed, maps the error to another value if it matches the given predicate, to recover into a successful AsyncResult
.
.recoverIfInstanceOf(errorClass, recoverFn)
​
If failed, maps the error to another value if it is an instance of the provided error class, to recover into a successful AsyncResult
.
.match(onSuccess, onFailure)
​
Maps the value if successful, or else the error if failed.
Same as .map(onSuccess).orElseGet(onFailure)
.
.orElse(value)
​
Alias for .recover(() => value)
to improve semantics, e.g. to indicate a default error handler at the end of a chain.
.orElseGet(() => value)
​
Alias for .recover(() => value)
to improve semantics, e.g. to indicate a default error handler at the end of a chain.
.asResult()
​
Converts the AsyncResult<T>
into a Promise<Result<T, unknown>>
. Can be used in combination with async/await to handle the Result
in a more imperative style.
Promise utilities ​
Most methods on AsyncResult
also exist as higher-order functions, so they can be applied on regular Promise
chains.
For example:
const value = await promiseTry(() => mightFail())
.then(map((r) => r * 2))
.catch(onFailure((e) => console.warn("Got error", e)))
.catch(recoverIfInstanceOf(MyCustomError, () => "fallback-1"))
.catch(orElse("fallback-2"));
promiseTry()
: creates a newPromise<T>
from any sync or async function- higher-order functions to be used with
.then()
:map()
: syntactic sugar to express a map operationonSuccess()
: to introduce a side effect upon success
- higher-order functions to be used with
.catch()
:onFailure()
: to introduce a side effect upon failureonFailureIfInstanceOf()
: to introduce a side effect only for certain failuresrecover()
: to recover from any failurerecoverIf()
: to recover only from specific failuresrecoverIfInstanceOf()
: to recover from certain types of failuresorElse()
: same asrecover
, to express a final fallback value;orElseGet()
: same asorElse
, but allows to provide an arrow function.
catchAll()
​
An additional .catch()
operator that applies all given callbacks onto the resulting Promise
, returning the aggregated result.
@catchErrors()
decorator ​
Backpack also offers a decorator which can be applied on methods that return a Promise<T>
. This decorator accepts one or more .catch()
callbacks, which will be applied in the provided order.
Example:
import { catchErrors } from "@backpack/error-handling/promises";
class MyService {
@catchErrors((e) => "fallback")
public async mightFail(): Promise<string> {
// ...
return "foo";
}
}
All the higher-order functions mentioned above, which are supposed to be used with .catch()
, can also be used in combination with the @catchErrors()
decorator:
class MyService {
@catchErrors(
onFailure((e) => console.error("Got error", e)),
recoverIfInstanceOf(MyError, () => "fallback 1"),
orElse("fallback-2"),
)
public async mightFail(): Promise<string> {
// ...
return "foo";
}
}
This also allows us to use Lambda error handlers, such as provided by @backpack/aws-lambda
, or by easily creating one yourself:
export class MyLambda {
@catchErrors(myErrorHandler())
public async handle(): Promise<APIGatewayProxyResult> {
// ...
}
}
const myErrorHandler = () =>
catchAll(
recoverIfInstanceOf(MyCustomError, myCustomErrorConverter()),
recoverIfInstanceOf(ProblemError, problemErrorConverter()),
orElseGet((error) =>
problemResult({
type: "/problems/internal-server-error",
title: "Internal Server Error",
detail: "Something went wrong.",
status: 500,
}),
),
);
About decorators
Native ECMAScript decorators are currently in stage 3 of the TC39 standardization process, and are not yet implemented in Node.js.
However, this is not a problem if your code is transpiled. By using Amazon Lambda Node.js CDK construct which uses esbuild under the hood, this is already taken care of.