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.
Installation
Section titled “Installation”To install the library, simply run the following command:
npm install --save @justomx/eitherWhy Use Either?
Section titled “Why Use 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.
Benefits of Using Either:
Section titled “Benefits of Using Either:”- 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:
- Error Handling with the Either Type
- Better Error Handling with Either Type in Dart
- Either vs Exception Handling
Inspiration
Section titled “Inspiration”The Either type in this library is inspired by similar concepts in functional programming libraries, such as Arrow in Kotlin.
Importing the Library
Section titled “Importing the Library”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) });Explanation:
Section titled “Explanation:”- EitherAsync.fromPromise: Wraps an asynchronous operation (
promiseFn) in anEitherAsyncinstance, which will capture both the success and failure cases. - map: Transforms the successful result (the
Rightvalue) by applying a function to it (convertToResponsein this case). - mapLeft: Handles the failure case (the
Leftvalue). If the error is a specific custom error (CustomError), we handle it withhandleCustomError; otherwise, the original error is returned. - run: Executes the async operation and returns an
Eithervalue. - fold: Processes the result:
- If there’s an error (
Left), it’s passed to thenextfunction (used in Express.js for error handling). - If the operation succeeds (
Right), the result is returned in the HTTP response.
- If there’s an error (
Use Case Scenarios:
Section titled “Use Case Scenarios:”- 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
Eitherto manage complex workflows where multiple operations may fail, enabling clearer error propagation and recovery.
Either Class
Section titled “Either Class”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.
Methods of the Either Class
Section titled “Methods of the Either Class”-
isLeft(): boolean- Checks if the instance is a
Leftvalue (error). - Returns
trueif the value is a failure (Left), andfalseotherwise.
- Checks if the instance is a
-
isRight(): boolean- Checks if the instance is a
Rightvalue (success). - Returns
trueif the value is a success (Right), andfalseotherwise.
- Checks if the instance is a
-
fold<T>(leftFn: (left: L) => T, rightFn: (right: R) => T): T- A pattern matching function that takes two functions: one for handling
Leftvalues and one forRightvalues. - Returns the result of applying the appropriate function based on the current value.
- A pattern matching function that takes two functions: one for handling
-
map<T>(fn: (r: R) => T): Either<L, T>- Transforms the successful result (
Right) by applying the functionfn. - Returns a new
Eitherinstance containing the transformed value.
- Transforms the successful result (
-
mapLeft<T>(fn: (l: L) => T): Either<T, R>- Transforms the failure value (
Left) by applying the functionfn. - Returns a new
Eitherinstance containing the transformedLeftvalue.
- Transforms the failure value (
-
flatMap<T>(fn: (right: R) => Either<L, T>): Either<L, T>- Similar to
map, but the functionfnreturns a new instance ofEither. - Enables chaining operations that can also fail.
- Similar to
-
flatMapLeft<T>(fn: (left: L) => Either<T, R>): Either<T, R>- Similar to
flatMap, but operates on theLeftvalue. - Allows chaining for handling error cases that can also return an
Either.
- Similar to
-
getOrThrow(errorMessage?: string): R- Returns the
Rightvalue 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.
- Returns the
-
getOrElse(defaultValue: R): R- Returns the
Rightvalue if present; otherwise, returns the provideddefaultValue. - Provides a fallback mechanism for handling errors.
- Returns the
-
getOrNull(): R | null- Returns the
Rightvalue if present; otherwise, returnsnull. - Useful for nullable types where
nullrepresents an error.
- Returns the
-
leftOrThrow(errorMessage?: string): L- Returns the
Leftvalue if present; otherwise, throws an error. - Useful for situations where the absence of a
Leftvalue is considered an error.
- Returns the
-
leftOrElse(defaultValue: L): L- Returns the
Leftvalue if present; otherwise, returns the provideddefaultValue. - Useful for providing fallback values for errors.
- Returns the
-
leftOrNull(): L | null- Returns the
Leftvalue if present; otherwise, returnsnull. - Similar to
getOrNull, but specifically forLeftvalues.
- Returns the
-
onLeft(action: (left: L) => void): Either<L, R>- Executes the provided action if the instance is a
Leftvalue. - Useful for side effects when dealing with errors.
- Executes the provided action if the instance is a
-
onRight(action: (right: R) => void): Either<L, R>- Executes the provided action if the instance is a
Rightvalue. - Useful for side effects when the operation is successful.
- Executes the provided action if the instance is a
-
static left<L, R>(value: L): Either<L, R>- Factory method to create a
Leftinstance. - Encapsulates an error value.
- Factory method to create a
-
static right<L, R>(value: R): Either<L, R>- Factory method to create a
Rightinstance. - Encapsulates a successful value.
- Factory method to create a
-
static catch<R>(fn: () => R): Either<Error, R>- Executes the provided function and captures any errors.
- Returns an
Eitherinstance representing success or failure.
Example code
Section titled “Example code”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);}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}`) });EitherAsync Class
Section titled “EitherAsync Class”The EitherAsync class extends the concept of Either for asynchronous operations. Below are the detailed descriptions of the methods available in the EitherAsync class.
Methods of the EitherAsync Class
Section titled “Methods of the EitherAsync Class”- **`map
- Transforms the successful result (
Right) of the asynchronous operation by applying the functionfn. - If the value is
Left, it maintains the value as is.
-
flatMap<T>(fn: (right: R) => Promise<Either<L, T>>): EitherAsync<L, T>- Similar to
map, but the functionfnreturns a new instance ofEitherwrapped in a promise. - Allows chaining multiple asynchronous operations while handling errors properly.
- Similar to
-
mapLeft<T>(fn: (left: L) => T): EitherAsync<T, R>- Transforms the failure value (
Left) by applying the functionfn. - If the value is
Right, it maintains the value without modification.
- Transforms the failure value (
-
flatMapLeft<T>(fn: (left: L) => Promise<Either<T, R>>): EitherAsync<T, R>- Similar to
flatMap, but operates on theLeftvalue. - Allows chaining for handling error cases that may also return an
Either.
- Similar to
-
onLeft(action: (left: L) => Promise<void> | void): EitherAsync<L, R>- Executes the provided action if the instance is a
Leftvalue, allowing for side effects like logging. - If the value is
Right, it does nothing.
- Executes the provided action if the instance is a
-
onRight(action: (right: R) => Promise<void> | void): EitherAsync<L, R>- Executes the provided action if the instance is a
Rightvalue. - Useful for side effects when the operation is successful.
- Executes the provided action if the instance is a
-
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.
- Executes the asynchronous operation and returns a promise that resolves to an
Static Methods
Section titled “Static Methods”-
static fromEither<L, R>(value: Either<L, R>): EitherAsync<L, R>- Converts an instance of
Eitherinto an instance ofEitherAsync, allowing it to be used in asynchronous flows.
- Converts an instance of
-
static fromPromise<L, R>(value: Promise<Either<L, R>>): EitherAsync<L, R>- Converts a promise that resolves to an
Eitherinto an instance ofEitherAsync, enabling the use ofEitherAsyncmethods.
- Converts a promise that resolves to an
Example code
Section titled “Example code”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));Conclusion
Section titled “Conclusion”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.