Heavily inspired by the Rust and Kotlin counterparts, this utility helps you with code that might fail in a declarative way.
Why?
Imagine we need a function that performs some kind of I/O task that might fail:
The problem with this 'usual' try-catch approach is that:
- it makes our code harder to reason about. We need to look at implementation details to discover what might go wrong.
- it makes the control flow of our code harder to reason about, especially with multiple (nested) try-catch statements
Instead, we could express the outcome of code to be executed in the form of a Result-type. People using your code will be explicitly confronted with the fact that code potentially might fail, and will know upfront what kind of errors they can expect.
Installation
npm install --save typescript-result
or
yarn add typescript-result
Usage
typescript-result exposes a single type:
;
Basically Result
is a container with a generic type: one for failure, and one for success:
Result<ErrorType, OkType>
Example
Let's refactor the readStuffFromFile()
a bit:
;
Static creation methods
Result.ok and Result.error
Result.safe
Functions as a try-catch, returning the return-value of the callback on success, or the predefined error(-class) or caught error on failure:
// with caught error...; // Result<Error, number> // with predefined error... ; // Result<CustomError, number> // with predefined error-class... ; // Result<CustomError, number>
Result.combine
Accepts multiple Results or functions that return Results and returns a singe Result. Successful values will be placed inside a tuple.
; // Result<Error | CustomError, [string, number, Date]> if result.isSuccess
Result.wrap
Transforms an existing function into a function that returns a Result:
; ; // number;; // Result<Error, number>;
Instance methods of Result
Result.isSuccess()
Indicates whether the Result is of type Ok. By doing this check you gain access to the encapsulated value
:
;if result.isSuccess else
Result.isFailure()
Indicates whether the Result is of type Error. By doing this check you gain access to the encapsulated error
:
;if result.isFailure else
Result.errorOrNull()
Returns the error on failure or null on success:
// on failure...;; // error is defined// on success...;; // error is null
Result.getOrNull()
Returns the value on success or null on failure:
// on success...;; // value is defined// on failure...;; // value is null
Result.fold(onSuccess: (value) => T, onFailure: (error) => T);
Returns the result of the onSuccess-callback for the encapsulated value if this instance represents success or the result of onFailure-callback for the encapsulated error if it is failure:
;;
Result.getOrDefault(value: T)
Returns the value on success or the return-value of the onFailure-callback on failure:
;;
Result.getOrElse(fn: (error) => T)
Returns the value on success or the return-value of the onFailure-callback on failure:
;;
Result.getOrThrow()
Returns the value on success or throws the error on failure:
;;
Result.map()
Maps a result to another result. If the result is success, it will call the callback-function with the encapsulated value, which returnr another Result. If the result is failure, it will ignore the callback-function, and will return the initial Result (error)
// nested results will flat-map to a single Result...; // Result<ErrorA | ErrorB, string> // ...or transform the successful value right away// note: underneath, the callback is wrapped inside Result.safe() in case the callback// might throw; // Result<ErrorA | Error, number>
Result.forward()
Creates and forwards a brand new Result out of the current error or value. This is useful if you want to return early after failure.
Rollbacks
There are cases where a series of operations are performed that need to be treated as a 'unit of work'. In other words: if the last operation fails, de preceding operations should also fail, despite the fact that those preceding operations succeeded on their own. In such cases you probably want some kind of recovering a.k.a. a rollback.
Fortunately, typescript-result allows you to rollback your changes with the minimum amount of effort.
Example
In this example we're dealing with user-data that needs to be saved within one transaction: