Skip to content

Either

@justomx/either is a library designed to provide a more functional approach to error handling in TypeScript. By using the Either type, you can explicitly represent computations that can result in two possible outcomes: a success or a failure. This avoids the pitfalls of traditional exception handling by making both success and failure cases part of your type system, leading to safer and more predictable code.

To install the library, simply run the following command:

Install dependency @justomx/either
npm install --save @justomx/either

The Either type represents a value that can be one of two possible types: a success (commonly called Right) or a failure (Left). It is particularly useful when working with asynchronous computations or operations that can result in errors.

Before Either became a common pattern, errors were often managed using exception handling. However, exception handling has several drawbacks:

  • Implicit error flow: Errors are thrown and caught in separate places, making the flow of logic harder to follow.
  • Unchecked exceptions: Many errors can slip through unchecked, leading to runtime issues.
  • Verbose error management: Error handling code often becomes bloated and repetitive.

With Either, these issues are mitigated by explicitly handling errors as part of the function’s return type. It forces the developer to handle both the success and failure cases in a clear and structured way.

  • Explicit error handling: Errors are no longer hidden or implicit; they are part of the function signature.
  • Type safety: You can enforce at the type level that both success and failure cases are handled.
  • Improved code readability: The code flow becomes clearer since success and failure paths are handled in one place.

Here are some resources that further explain the benefits of using Either over traditional exception handling:

The Either type in this library is inspired by similar concepts in functional programming libraries, such as Arrow in Kotlin.

To start using the Either type in your TypeScript project, import the necessary classes from @justomx/either:

import { Either, EitherAsync } from '@justomx/either';

Example of Using Either for Error Handling

Section titled “Example of Using Either for Error Handling”

Here’s a typical example of how you can use EitherAsync to manage asynchronous operations that may fail:

const result = await EitherAsync.fromPromise(promiseFn)
.map((data) => convertToResponse(data))
.mapLeft((error) => {
if (error instanceof CustomError) {
return handleCustomError(error);
}
return error;
})
.run();
result.fold(
(err) => {
next(err)
},
(data) => {
res.status(200).json(data)
}
);
  1. EitherAsync.fromPromise: Wraps an asynchronous operation (promiseFn) in an EitherAsync instance, which will capture both the success and failure cases.
  2. map: Transforms the successful result (the Right value) by applying a function to it (convertToResponse in this case).
  3. mapLeft: Handles the failure case (the Left value). If the error is a specific custom error (CustomError), we handle it with handleCustomError; otherwise, the original error is returned.
  4. run: Executes the async operation and returns an Either value.
  5. fold: Processes the result:
    • If there’s an error (Left), it’s passed to the next function (used in Express.js for error handling).
    • If the operation succeeds (Right), the result is returned in the HTTP response.
  • API requests: Handle responses from external APIs that might return errors, ensuring both success and failure cases are covered.
  • Database operations: Safely handle database queries that can either succeed or fail without crashing the application.
  • Complex business logic: Use Either to manage complex workflows where multiple operations may fail, enabling clearer error propagation and recovery.

The Either class is a fundamental part of this library, allowing for explicit error handling in a functional style. Below are the detailed descriptions of the methods available in the Either class.

  1. isLeft(): boolean

    • Checks if the instance is a Left value (error).
    • Returns true if the value is a failure (Left), and false otherwise.
  2. isRight(): boolean

    • Checks if the instance is a Right value (success).
    • Returns true if the value is a success (Right), and false otherwise.
  3. fold<T>(leftFn: (left: L) => T, rightFn: (right: R) => T): T

    • A pattern matching function that takes two functions: one for handling Left values and one for Right values.
    • Returns the result of applying the appropriate function based on the current value.
  4. map<T>(fn: (r: R) => T): Either<L, T>

    • Transforms the successful result (Right) by applying the function fn.
    • Returns a new Either instance containing the transformed value.
  5. mapLeft<T>(fn: (l: L) => T): Either<T, R>

    • Transforms the failure value (Left) by applying the function fn.
    • Returns a new Either instance containing the transformed Left value.
  6. flatMap<T>(fn: (right: R) => Either<L, T>): Either<L, T>

    • Similar to map, but the function fn returns a new instance of Either.
    • Enables chaining operations that can also fail.
  7. flatMapLeft<T>(fn: (left: L) => Either<T, R>): Either<T, R>

    • Similar to flatMap, but operates on the Left value.
    • Allows chaining for handling error cases that can also return an Either.
  8. getOrThrow(errorMessage?: string): R

    • Returns the Right value if present; otherwise, throws an error with the provided message or a default message.
    • Useful for cases where you want to fail fast on errors.
  9. getOrElse(defaultValue: R): R

    • Returns the Right value if present; otherwise, returns the provided defaultValue.
    • Provides a fallback mechanism for handling errors.
  10. getOrNull(): R | null

    • Returns the Right value if present; otherwise, returns null.
    • Useful for nullable types where null represents an error.
  11. leftOrThrow(errorMessage?: string): L

    • Returns the Left value if present; otherwise, throws an error.
    • Useful for situations where the absence of a Left value is considered an error.
  12. leftOrElse(defaultValue: L): L

    • Returns the Left value if present; otherwise, returns the provided defaultValue.
    • Useful for providing fallback values for errors.
  13. leftOrNull(): L | null

    • Returns the Left value if present; otherwise, returns null.
    • Similar to getOrNull, but specifically for Left values.
  14. onLeft(action: (left: L) => void): Either<L, R>

    • Executes the provided action if the instance is a Left value.
    • Useful for side effects when dealing with errors.
  15. onRight(action: (right: R) => void): Either<L, R>

    • Executes the provided action if the instance is a Right value.
    • Useful for side effects when the operation is successful.
  16. static left<L, R>(value: L): Either<L, R>

    • Factory method to create a Left instance.
    • Encapsulates an error value.
  17. static right<L, R>(value: R): Either<L, R>

    • Factory method to create a Right instance.
    • Encapsulates a successful value.
  18. static catch<R>(fn: () => R): Either<Error, R>

    • Executes the provided function and captures any errors.
    • Returns an Either instance representing success or failure.
src/utils/divide.ts
import { Either } from '@justomx/either';
export function divide(a: number, b: number): Either<string, number> {
if (b === 0) {
return Either.left('Error: Division by zero');
}
return Either.right(a / b);
}
src/index.ts
import { divide } from './utils/divide';
const result = divide(10, 2);
result.fold(
(error) => { console.error(error) },
(value) => { console.log(`Result: ${value}`) }
);
const errorResult = divide(10, 0);
errorResult.fold(
(error) => { console.error(error) },
(value) => { console.log(`Result: ${value}`) }
);

The EitherAsync class extends the concept of Either for asynchronous operations. Below are the detailed descriptions of the methods available in the EitherAsync class.

  1. **`map

(fn: (right: R) => T): EitherAsync<L, T>`**

  • Transforms the successful result (Right) of the asynchronous operation by applying the function fn.
  • If the value is Left, it maintains the value as is.
  1. flatMap<T>(fn: (right: R) => Promise<Either<L, T>>): EitherAsync<L, T>

    • Similar to map, but the function fn returns a new instance of Either wrapped in a promise.
    • Allows chaining multiple asynchronous operations while handling errors properly.
  2. mapLeft<T>(fn: (left: L) => T): EitherAsync<T, R>

    • Transforms the failure value (Left) by applying the function fn.
    • If the value is Right, it maintains the value without modification.
  3. flatMapLeft<T>(fn: (left: L) => Promise<Either<T, R>>): EitherAsync<T, R>

    • Similar to flatMap, but operates on the Left value.
    • Allows chaining for handling error cases that may also return an Either.
  4. onLeft(action: (left: L) => Promise<void> | void): EitherAsync<L, R>

    • Executes the provided action if the instance is a Left value, allowing for side effects like logging.
    • If the value is Right, it does nothing.
  5. onRight(action: (right: R) => Promise<void> | void): EitherAsync<L, R>

    • Executes the provided action if the instance is a Right value.
    • Useful for side effects when the operation is successful.
  6. async run(): Promise<Either<L, R>>

    • Executes the asynchronous operation and returns a promise that resolves to an Either<L, R>.
    • This method materializes the final result of the asynchronous flow.
  1. static fromEither<L, R>(value: Either<L, R>): EitherAsync<L, R>

    • Converts an instance of Either into an instance of EitherAsync, allowing it to be used in asynchronous flows.
  2. static fromPromise<L, R>(value: Promise<Either<L, R>>): EitherAsync<L, R>

    • Converts a promise that resolves to an Either into an instance of EitherAsync, enabling the use of EitherAsync methods.
src/infrastructure/services/client.ts
const result = await EitherAsync.fromPromise(fetchData())
.map((data) => transformData(data))
.mapLeft((error) => logError(error))
.onRight((data) => console.log('Success:', data))
.onLeft((error) => console.error('Error:', error))
.run();
result.fold(
(err) => handleError(err),
(data) => handleSuccess(data)
);

The @justomx/either library provides a powerful tool for managing errors and results in a functional style using the Either and EitherAsync types. By making error handling explicit and type-safe, developers can write cleaner and more predictable code that is easier to maintain and understand.

For further information, feel free to reach out or check the repository’s README file.