EitherAsync

ChainAlt
It is recommended to have your promises resolve to Either wherever error handling is needed instead of rejecting them and handling errors in the catch method, because unfortunately errors in Promises are not typechecked. EitherAsync lets you do that seamlessly, it's a wrapper (and hopefully a drop-in replacement) of Promise<Either<L, R>> that allows you to process asynchronous values while also having error handling via Either. EitherAsync even implementsPromiseLike, so you want await it just like a regular Promise.

That said, there are 2 ways of composing EitherAsync values, just like there are two ways of working with Promises - async/await and chaining together transformations. If you squint hard you can see that EitherAsync tries really hard to be a monad transformer, which is true, but constraining it to Promises brought a tremendous amount of value and the trade-off is worth it.
How to import
import { EitherAsync } from 'purify-ts/EitherAsync'
Given the following functions, examples below
function validateRequest(req: Request): Either<Error, DeleteUserRequest>
function getUser(userId: number):       Promise<Either<Error, User>>
function deleteUserDb(user: User):      Promise<Id<User>>
Example usage (async/await)
const deleteUser = (req): EitherAsync<Error, Id<User>> =>
    EitherAsync(async ({ liftEither, fromPromise, throwE }) => {
       // when you have Either<L, R> and you want to get R out
       const request = await liftEither(validateRequest(req))

       try {
           // when you have Promise<Either<L, R>> and you want to get R out
           const user = await fromPromise(getUser(request.userId))
       } catch {
           throwE(Error.UserDoesNotExist)
       }

       return deleteUserDb(user)
    })

const promise: Promise<Either<Error, Id<User>>> =
   deleteUser(req).run()
Example usage (chaining)
const deleteUser = (req): EitherAsync<Error, Id<User>> =>
    EitherAsync.liftEither(validateRequest(req))
        // when you have 
        // Promise<Either<L, R>> or EitherAsync<L, R> (both work)
        // and you want to chain it
        .chain(request => getUser(request.userId))
        .mapLeft(_     => Error.UserDoesNotExist)

        // when you have Promise<T> and you want to chain it
        .chain(user    => EitherAsync(() => deleteUserDb(user)))

const promise: Promise<Either<Error, Id<User>>> =
   deleteUser(req).run()

Constructors

EitherAsync
(EitherAsyncHelpers -> IO a) -> EitherAsync a b<L, R>(runPromise: (helpers: EitherAsyncHelpers<L>) => PromiseLike<R>): EitherAsync<L, R>
Constructs an EitherAsync object from a function that takes an object full of helpers that let you lift things into the EitherAsync context and returns a Promise.
EitherAsync<Error, number>(({ liftEither, fromPromise }) => Promise.resolve(5))
EitherAsync<Error, number>

Static methods

fromPromise
(() -> IO (Either a b)) -> EitherAsync a b<L, R>(f: () => PromiseLike<Either<L, R>>): EitherAsync<L, R>
Constructs an EitherAsync object from a function that returns an Either wrapped in a Promise. You would rarely need to do that since most of the EitherAsync API works with both EitherAsync and Promise values.
EitherAsync.fromPromise(() => Promise.resolve(Right(5)))
EitherAsync<never, number>
liftEither
Either a b -> EitherAsync a b<L, R>(either: Either<L, R>): EitherAsync<L, R>
Constructs an EitherAsync object from an Either.
EitherAsync.liftEither(Right(5))
EitherAsync<never, number>
lefts
[EitherAsync a b] -> [IO a]<L, R>(list: EitherAsync<L, R>[]): Promise<L[]>
Takes a list of `EitherAsync`s and returns a Promise that will resolve with all `Left` values. Internally it uses `Promise.all` to wait for all results.
EitherAsync.lefts([
  EitherAsync.liftEither(Left('Server error')),
  EitherAsync.liftEither(Left('Wrong password')),
  EitherAsync(async () => 'foo@bar.com')
])
Promise {<resolved>: ['Server error', 'Wrong password']}
rights
[EitherAsync a b] -> [IO b]<L, R>(list: Either<L, R>[]): R[]
Takes a list of `EitherAsync`s and returns a Promise that will resolve with all `Right` values. Internally it uses `Promise.all` to wait for all results.
EitherAsync.rights([
  EitherAsync(async () => 10),
  EitherAsync.liftEither(Left('Invalid input')),
  EitherAsync(async () => 5)
])
Promise {<resolved>: [10, 5]}
sequence
[EitherAsync a b] -> EitherAsync a [b]<L, R>(eas: EitherAsync<L, R>[]): EitherAsync<L, R[]>
Turns a list of `EitherAsync`s into an `EitherAsync` of list. The returned `Promise` will be rejected as soon as a single `EitherAsync` resolves to a `Left`, it will not wait for all Promises to resolve and since `EitherAsync` is lazy, unlike `Promise`, the remaining async operations will not be executed at all.
EitherAsync.sequence([EitherAsync(async () => 1), EitherAsync(async () => 2)).run()
EitherAsync.sequence([EitherAsync(async () => 1), EitherAsync(() => { throw 'Error' })])).run()
Promise {<resolved>: Right([1, 2])}
Promise {<resolved>: Left('Error')}
all
[EitherAsync a b] -> EitherAsync a [b]<L, R>(eas: EitherAsync<L, R>[]): EitherAsync<L, R[]>
The same as `EitherAsync.sequence`, but it will run all async operations at the same time rather than sequentially.

Instance methods

run
EitherAsync a b ~> IO (Either a b)run(): Promise<Either<L, R>>
IMPORTANT: The Promise returned from `run` will never be rejected, so there's no point in calling `catch` on it. If something goes wrong, here's how it's going to be handled:
  • If any of the computations inside EitherAsync resolved to a Left, `run` will return a Promise resolved to that Left.
  • If any of the promises were to be rejected then `run` will return a Promise resolved to a Left with the rejection value inside.
  • If an exception is thrown then `run` will return a Promise resolved to a Left with the exception inside.
  • If none of the above happen then a promise resolved to the returned value wrapped in a Right will be returned.
EitherAsync<string, never>(({ liftEither }) => liftEither(Left('Error'))).run()
EitherAsync<string, never>(() => Promise.reject('Something happened')).run()
EitherAsync<Error, never>(() => { throw Error('Something happened') }).run()
EitherAsync<string, number>(() => Promise.resolve(5)).run()
Promise {<resolved>: Left('Error')}
Promise {<resolved>: Left('Something happened')}
Promise {<resolved>: Left(Error('Something happened'))}
Promise {<resolved>: Right(5)}
bimap
EitherAsync a b ~> (a -> c) -> (b -> d) -> EitherAsync c d<L2, R2>(f: (value: L) => L2, g: (value: R) => R2): EitherAsync<Awaited<L2>, Awaited<R2>>
Given two functions, maps the value that the Promise inside `this` resolves to using the first if it is `Left` or using the second one if it is `Right`.
EitherAsync(() => Promise.resolve(5)).bimap(identity, x => x + 1).run()
EitherAsync(() => Promise.reject(5)).bimap(x => x + 1, identity).run()
Promise {<resolved>: Right(6)}
Promise {<resolved>: Left(6)}
map
EitherAsync a b ~> (b -> c) -> EitherAsync a c<R2>(f: (value: R) => R2): EitherAsync<L, Awaited<R2>>
Transforms the `Right` value of `this` with a given function. If the EitherAsync that is being mapped resolves to a Left then the mapping function won't be called and `run` will resolve the whole thing to that Left, just like the regular Either#map.
EitherAsync(() => Promise.resolve(5)).map(x => x + 1).run()
Promise {<resolved>: Right(6)}
mapLeft
EitherAsync a b ~> (a -> c) -> EitherAsync c b<L2>(f: (value: L) => L2): EitherAsync<Awaited<L2>, R>
Maps the `Left` value of `this`, acts like an identity if `this` is `Right`.
EitherAsync(() => throw new Error()).mapLeft(_ => ({ status: 500 })).run()
EitherAsync(() => Promise.resolve(5)).mapLeft(x => x + 1).run()
Promise {<resolved>: Left({ status: 500 })}
Promise {<resolved>: Right(5)}
ap
<L2, R2>(other: PromiseLike<Either<L2, (value: R) => R2>>): EitherAsync<L | L2, Awaited<R2>>
Applies a `Right` function wrapped in `EitherAsync` over a future `Right` value. Returns `Left` if either the `this` resolves to a `Left` or the function is `Left`.
chain
<L2, R2>(f: (value: R) => PromiseLike<Either<L2, R2>>): EitherAsync<L | L2, R2>
Transforms `this` with a function that returns a `EitherAsync` or another `PromiseLike`. Behaviour is the same as the regular Either#chain.
EitherAsync(async () => 5).chain(x => EitherAsync(async () => x + 1)).run()
EitherAsync(async () => 5).chain(async (x) => Right(x + 1)).run()
Promise {<resolved>: Right(6)}
Promise {<resolved>: Right(6)}
chainLeft
<L2, R2>(f: (value: L) => PromiseLike<Either<L2, R2>>): EitherAsync<L2, R | R2>
The same as EitherAsync#chain but executes the transformation function only if the value is Left. Useful for recovering from errors asynchronously.
EitherAsync(({ throwE }) => throwE(500))
  .chainLeft(x => EitherAsync(() => Promise.resolve(6)))
  .run()
Promise {<resolved>: Right(6)}
join
EitherAsync a (Either a b) ~> EitherAsync a b<L2, R2>(this: EitherAsync<L, Either<L2, R2>>): EitherAsync<L | L2, R2>
Flattens an `Either` nested inside an `EitherAsync`. `e.join()` is equivalent to `e.chain(async x => x)`.
alt
EitherAsync a b ~> EitherAsync a b -> EitherAsync a b(other: EitherAsync<L, R>): EitherAsync<L, R>
Returns the first `Right` between the future value of `this` and another `EitherAsync` or the `Left` in the argument if both `this` and the argument resolve to `Left`.
extend
EitherAsync a b ~> (EitherAsync a b -> c) -> EitherAsync a c<R2>(f: (value: EitherAsync<L, R>) => R2): EitherAsync<L, Awaited<R2>>
Returns `this` if it resolves to a `Left`, otherwise it returns the result of applying the function argument to `this` and wrapping it in a `Right`.
orDefault
EitherAsync a b ~> b -> b(defaultValue: R): Promise<R>
Returns a Promise that resolves to the value inside `this` if it's `Right` or a default value if `this` is `Left`.
leftOrDefault
EitherAsync a b ~> a -> IO a(defaultValue: L): Promise<L>
Returns a Promise that resolves to the value inside `this` if it's `Left` or a default value if `this` is `Right`.
toMaybeAsync
EitherAsync a b ~> MaybeAsync b(): MaybeAsync<R>
Converts `this` to a MaybeAsync, discarding any error values.
swap
EitherAsync a b ~> EitherAsync b a(): EitherAsync<R, L>
Returns `Right` if `this` is `Left` and vice versa.
EitherAsync<string, number>(() => Promise.resolve(5)).swap().run()
EitherAsync(() => Promise.reject('Something happened')).swap().run()
Promise {<resolved>: Left(5)}
Promise {<resolved>: Right('Something happened')}
ifLeft
(effect: (value: L) => any): EitherAsync<L, R>
Runs an effect if `this` is `Left`, returns `this` to make chaining other methods possible.
EitherAsync.liftEither(Left('Error')).ifLeft((err) => console.log(err))
EitherAsync.liftEither(Right(5)).ifLeft(() => console.log('Unexpected error'))
// Error
ifRight
(effect: (value: R) => any): EitherAsync<L, R>
Runs an effect if `this` is `Right`, returns `this` to make chaining other methods possible.
EitherAsync.liftEither(Left('Error')).ifRight((result) => console.log(result))
EitherAsync.liftEither(Right(5)).ifRight((result) => console.log(result))
// 5
finally
(effect: () => any): EitherAsync<L, R>
Similar to the Promise method of the same name, the provided function is called when the `EitherAsync` is executed regardless of whether the `Either` result is `Left` or `Right`.
EitherAsync.liftEither(Left('Error')).finally(() => console.log('It runs!))
EitherAsync.liftEither(Right(5)).finally(() => console.log('It runs!))
// It runs!
// It runs!
void
(): EitherAsync<L, void>
Useful if you are not interested in the result of an operation.

Methods passed to the EitherAsync async/await callback

liftEither
Either a b -> EitherAsyncValue b<L, R>(either: Either<L, R>): EitherAsyncValue<R>
This helper is passed to the function given to the EitherAsync constructor. It allows you to take a regular Either value and lift it to the EitherAsync context. Awaiting a lifted Either will give you the `Right` value inside. If the Either is Left then the function will exit immediately and EitherAsync will resolve to that Left after running it.
EitherAsync(async ({ liftEither }) => {
  const value: number = await liftEither(Right(5))
}).run()
Promise {<resolved>: Right(5)}
fromPromise
IO (Either a b) -> EitherAsyncValue b<L, R>(promise: PromiseLike<Either<L, R>>): EitherAsyncValue<R>
This helper is passed to the function given to the EitherAsync constructor. It allows you to take an Either inside a Promise and lift it to the EitherAsync context. Awaiting a lifted Promise<Either> will give you the `Right` value inside the Either. If the Either is Left or the Promise is rejected then the function will exit immediately and MaybeAsync will resolve to that Left or the rejection value after running it.
EitherAsync(async ({ fromPromise }) => {
  const value: number = await fromPromise(Promise.resolve(Right(5)))
}).run()
Promise {<resolved>: Right(5)}
throwE
(error: L): never
This helper is passed to the function given to the EitherAsync constructor. A type safe version of throwing an exception. Unlike the Error constructor, which will take anything, `throwE` only accepts values of the same type as the Left part of the Either.
EitherAsync<string, number>(async ({ liftEither, throwE })
  const value: number = await liftEither(Right(5))
  throwE('Test')
  return value
}).run()
Promise {<resolved>: Left('Test')}