@flagg2/result
TypeScript icon, indicating that this package has built-in type declarations

1.8.2 • Public • Published

@flagg2/result

npm npm npm package minimized gzipped size (select exports) NPM

This library provides a Result type for Typescript, allowing for better and safer error handling.

Table of Contents

Features

  • Rust-like Result type
  • Better error handling
  • Automatic type inference
  • More robust code
  • Zero dependencies
  • Minimalistic
  • Small package size

Usage

Imagine having a function which you use to split time into seconds and minutes. We will look at one implementation which uses result and a second one which does not.

// returns {hours: number, mins: number}
function parseTime(time: string) {
   const splitTime = time.split(":")

   return {
      hours: parseInt(splitTime[0], 10),
      mins: parseInt(splitTime[1], 10),
   }
}

Now you call parseTime in a different place of our codebase. This function uses a .split and relies on the result being at least 2 items long.

Because of that, you have to keep in mind that this function could throw, even though there is no indication by the type system that it could be the case.

This leads to uncaught errors.

Somehow, the function get called with an incorrect argument, for example "2051" instead of "20:51".

This arugment is however still a string which makes typescript unable to help us catch this error.

function faultyArgument() {
   const time = "2051"

   const result = splitTime(time)

   // You do not have any indication by the type system that this could throw.
   // You forget to use a try catch segment and end up with a runtime error

   return result
}

This is when the Result class comes in. Result indicates a computation which could fail. At runtime, could be either an Ok or an Err depending on cirumstances.

The massive benefit we get with Result is that we do not catch errors like the previously mentioned one at runtime , but rather at compilation time .

Let's look at the previous example with Result

function parseTime(time: string) {
   const splitTime = time.split(":")
   if (splitTime.length !== 2) {
      return Result.err("SPLIT_ERROR")
   }

   if (isNaN(parseInt(splitTime[0], 10)) || isNaN(parseInt(splitTime[1], 10))) {
      return Result.err("PARSE_ERROR")
   }

   if (parseInt(splitTime[0], 10) > 23 || parseInt(splitTime[1], 10) > 59) {
      return Result.err("VALUE_ERROR")
   }

   return Result.ok({
      hours: parseInt(splitTime[0], 10),
      mins: parseInt(splitTime[1], 10),
   })
}

Now, using the Result pattern, we are forced to deal with the fact that it could fail at compilation time .

Better yet, we know exactly which errors can occur and we can handle them accordingly.

For example:

function faultyArgument() {
   const time = "2051"

   const result = parseTime(time)
   // type is Result<{hours: number, mins: number}, "SPLIT_ERROR" | "PARSE_ERROR" | "VALUE_ERROR">

   // Here you gracefully handle the error case

   if (result.isErr()) {
      // errValue is only available after the type system is sure that the result is an Err
      switch (result.errValue) {
         case "SPLIT_ERROR":
            console.log("The time was not in the correct format")
            break
         case "PARSE_ERROR":
            console.log("The time contained non-numeric characters")
            break
         case "VALUE_ERROR":
            console.log("The time contained invalid values")
            break
      }

      return
   }

   // Here the type system is sure that the result is an Ok, and we get access to the "value" property

   const { hours, mins } = result.value

   console.log(`The time is ${hours}:${mins}`)
}

As you can see, it is much harder to shoot yourself in the foot while handling errors, making our code much more robust.

Whenever possible, the result return type gets inferred automatically for the best dev experience possible.

Base Classes

Result<T, E>

A class representing a computation which may succeed or fail.

Ok<T>

A class representing a successful computation.

Err<E>

A class representing a failed computation.

API

Result

Result.ok()

Creates a new Ok variant; If no value is provided, it defaults to null.

 static ok<T>(value?: T): Ok<T>

Result.err()

Creates a new Err variant. If no value is provided, it defaults to null. Optionally takes an origin argument which is the original error that was thrown.

 static err<E>(errValue?: E, origin?: Error): Err<E>

Result.from()

Creates a Result from a function, a promise, or a promise-returning function.

If an error is thrown at any point, it is caught and wrapped in an Err. Takes an optional catchFn argument which should be a function returning the value contained in the Err variant.

If the function or promise resolves successfully, the value will be wrapped in an Ok.

 static from<T, E>(fnOrPromise: (() => T | Promise<T>) | Promise<T>, catchFn = (err: Error) => null as E): Promise<Result<T>>

Result.tryCatch()

Wraps a function that returns a Result but may still throw an error, in which case it is caught and wrapped in and Err from the catchFn.

static tryCatch<T, const E = null>(fn: () => Result<T, E> | Promise<Result<T, E>>,catchFn: (err: Error) => E = () => null as E)

Result.infer()

Sometimes type inference does not work well with Result unions. You might notice that your arguments are being inferred as any or that the return types are not correct.

This can be the case when using andThen , map , mapErr , or match .

When this happens, call this function to get a type that is easier to work with.

 static infer<T extends Result>(result: T): T

Result.all()

Takes an array of Result instances and returns a single Result instance containing an array of all the Ok values.

If any of the results are an Err, the first Err value is returned.

 static all<T>(results: Result<T>[]): Result<[...]>

Result.isOk()

Returns true if the result is an Ok variant. If true, casts the result as Ok

   isOk(): this is Ok<T>

Result.isErr()

Returns true if the result is an Err variant. If true, casts the result as Err

   isErr(): this is Err<E>

Result.unwrap()

Returns the contained Ok value. Throws an error if the value is an Err.

   unwrap(): T

Result.unwrapErr()

Returns the contained Err value. Throws an error if the value is an Ok.

   unwrapErr(): E

Result.unwrapOr()

Returns the contained Ok value. If the value is an Err, returns the provided default value.

   unwrapOr(defaultValue: T): T

Result.expect()

Returns the contained Ok value. If the value is an Err, throws an error with the provided message.

   expect(message: string): T

Result.expectErr()

Returns the contained Err value. If the value is an Ok, throws an error with the provided message.

   expectErr(message: string): E

Result.match()

Calls the appropriate function based on the result based on if it is an Ok or an Err.

   match<U>(fn: { ok: (value: T) => U; err: (errValue: E) => U }): U

Result.andThen()

Calls the provided function if the result is an Ok. If the result is an Err, returns the Err value.

   andThen<U>(fn: (value: T) => Result<U, E>): Result<U, E>

Result.map()

Maps a Result<T, E> to Result<U, E> by applying a function to a contained Ok value, leaving an Err value untouched.

   map<U>(fn: (value: T) => U): Result<U, E>

Result.mapErr()

Maps a Result<T, E> to Result<T, F> by applying a function to a contained Err value, leaving an Ok value untouched.

   mapErr<F>(fn: (errValue: E) => F): Result<T, F>

Result.logIfErr()

Logs the error to the console if the result is an Err.

   logIfErr(): this

Ok

Ok.value

The value contained in the Ok variant.

value: T

Err

Err.errValue

The value contained in the Err variant.

errValue: E

Err.cause

A chain of Result instances that led to the error. Might have a Error instance at the end of the chain.

origin: Cause

Err.log()

Logs the error to the console.

   log(): this

Err.getTrace(): string

Returns a string representation of the error chain.

   getTrace(): string

Err.getCause(): Cause

Returns the cause of the error.

   getCause(): Cause

Err.getRootCause(): Cause

Returns the root cause of the error.

   getRootCause(): Cause

Package Sidebar

Install

npm i @flagg2/result

Weekly Downloads

266

Version

1.8.2

License

MIT

Unpacked Size

330 kB

Total Files

32

Last publish

Collaborators

  • flagg2