@quintal/result
v0.2.0
Published
A TypeScript error handling paradigm using a `Result` monad, inspired by the Rust programming language. For an amazing explanation on Monads, [check this video by Studying With Alex](https://www.youtube.com/watch?v=C2w45qRc3aU)
Downloads
6
Readme
@quintal/result
A TypeScript error handling paradigm using a Result
monad, inspired by the
Rust programming language. For an amazing explanation on Monads,
check this video by Studying With Alex
Instead of returning a value from a function that can error, you return a
Result<T, E>
value, which represents either a value of type T
if the
function did not error, or an error of type E
if it did.
As an example, this would be some error-prone code:
async function checkUserPassword(
username: string,
password: string,
): Promise<boolean> {
// This could throw an unexpected error if something goes wrong with the database connection.
const users = await db
.select(users)
.where({ username: eq(users.username, username) });
if (users.length === 0) {
// Explicity throw error, making for an unpredictable control flow.
throw new Error('username unknown');
}
const user = user[0]!;
// Etc.
}
Rewriting this code to use the Result
monad would look something like this:
import {
type AsyncResult,
ok,
err,
asyncResultWrap,
runResult,
} from '@quintal/result';
enum CheckUserPasswordError {
UNKNOWN_USERNAME,
}
async function checkUserPassword(
username: string,
password: string,
): AsyncResult<boolean, CheckUserPasswordError> {
// Wrap the dangerous db call with `asyncResultWrap` to catch the error if it's thrown.
// Users is now of type Result<User[], unknown>
const usersResult = await asyncResultWrap(() =>
db.select(users).where({ username: eq(users.username, username) }),
);
// Assume that `users` is a value, not an error.
// If users is an error, this function will not run and just return this same error.
const user = runResult(usersResult, (users) => {
// We no longer throw, we return `err()`, which wraps the given error in the `Result` monad.
if (users.length === 0) return err(CheckUserPasswordError.UNKNOWN_USERNAME);
return ok(user[0]!);
});
// Etc.
}
Not only did we, with a very minor code overhead, make the first code snippet a lot safer (we anticipate that the database may throw an unexpected error), we don't disrupt the control flow by explicitly throwing an error, making for more performant code.