MaybeAsync

ChainAltFilterable
MaybeAsync is a wrapper (and hopefully a drop-in replacement) of Promise<Maybe<T>>that allows you to process asynchronous values while also having error handling via Maybe. MaybeAsync even implementsPromiseLike, so you can await it just like a regular Promise.

That said, there are 2 ways of composing MaybeAsync 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 MaybeAsync 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 { MaybeAsync } from 'purify-ts/MaybeAsync'
Given the following functions, examples below
function validateRequest(req: Request): Maybe<DeleteUserRequest>
function getUser(userId: number):       Promise<Maybe<User>>
function deleteUserDb(user: User):      Promise<Id<User>>
Example usage (async/await)
const deleteUser = (req): MaybeAsync<Id<User>> =>
    MaybeAsync(async ({ liftMaybe, fromPromise }) => {
        // when you have Maybe<T> and you want to get T out
        const request = await liftMaybe(validateRequest(req))

        // when you have Promise<Maybe<T>> and you want to get T out
        const user    = await fromPromise(getUser(request.userId))

        return deleteUserDb(user)
    })

const promise: Promise<Maybe<Id<User>>> = deleteUser(req).run()
Example usage (chaining)
const deleteUser = (req): MaybeAsync<Id<User>> =>
    MaybeAsync.liftMaybe(validateRequest(req))
        // Promise<Maybe<T>> or MaybeAsync<T> (both work)
        // and you want to chain it
        .chain(request => getUser(request.userId))

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

const promise: Promise<Maybe<Id<User>>> = deleteUser(req).run()

Constructors

MaybeAsync
(MaybeAsyncHelpers -> IO a) -> MaybeAsync a<T>(runPromise: (helpers: MaybeAsyncHelpers) => PromiseLike<T>): MaybeAsync<T>
Constructs a MaybeAsync object from a function that takes an object full of helpers that let you lift things into the MaybeAsync context and returns a Promise.
MaybeAsync(({ liftMaybe, fromPromise }) => Promise.resolve(5))
MaybeAsync<number>

Static methods

fromPromise
(() -> IO (Maybe a)) -> MaybeAsync a<T>(f: () => Promise<Maybe<T>>): MaybeAsync<T>
Constructs an MaybeAsync object from a function that returns a Maybe wrapped in a Promise. It is recommended to stick to one style of using MaybeAsync only as you will run into nasty variable shadowing if you use the helpers for async/await while you have any of the constructors imported.
MaybeAsync.fromPromise(() => Promise.resolve(Just(5)))
MaybeAsync<number>
liftMaybe
Maybe a -> MaybeAsync a<T>(maybe: Maybe<T>): MaybeAsync<T>
Constructs an MaybeAsync object from a Maybe.
MaybeAsync.liftMaybe(Just(5))
MaybeAsync<number>
catMaybes
[MaybeAsync a] -> IO [a]<T>(list: MaybeAsync<T>[]): Promise<T[]>
Takes a list of `MaybeAsync`s and returns a Promise that will resolve with all `Just` values. Internally it uses `Promise.all` to wait for all results.
MaybeAsync.catMaybes([
  MaybeAsync(async () => 'value1'),
  MaybeAsync.liftMaybe(Nothing),
  MaybeAsync(async () => 'value2')
])
Promise {<resolved>: ['value1', 'value2']}

Instance methods

run
MaybeAsync a ~> IO (Maybe a)run(): Promise<Maybe<T>>
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 MaybeAsync resolved to Nothing, `run` will return a Promise resolved to Nothing.
  • If any of the promises were to be rejected then `run` will return a Promise resolved to Nothing.
  • If an exception is thrown then `run` will return a Promise resolved to Nothing.
  • If none of the above happen then a promise resolved to the returned value wrapped in a Just will be returned.
MaybeAsync(async ({ liftMaybe }) => liftMaybe(Nothing)).run()
MaybeAsync(() => Promise.reject()).run()
MaybeAsync(() => { throw Error('Something happened') }).run()
MaybeAsync(() => Promise.resolve(5)).run()
Promise {<resolved>: Nothing}
Promise {<resolved>: Nothing}
Promise {<resolved>: Nothing}
Promise {<resolved>: Just(5)}
map
MaybeAsync a ~> (a -> b) -> MaybeAsync b<U>(f: (value: T) => U): MaybeAsync<Awaited<U>>
Transforms the value inside `this` with a given function. If the MaybeAsync that is being mapped resolves to Nothing then the mapping function won't be called and `run` will resolve the whole thing to Nothing, just like the regular Maybe#map.
MaybeAsync(() => Promise.resolve(5)).map(x => x + 1).run()
Promise {<resolved>: Just(6)}
chain
<U>(f: (value: T) => PromiseLike<Maybe<U>>): MaybeAsync<U>
Transforms `this` with a function that returns a `MaybeAsync` or another `PromiseLike`. Behaviour is the same as the regular Maybe#chain.
MaybeAsync(async () => 5).chain(x => MaybeAsync(async () => x + 1)).run()
MaybeAsync(async () => 5).chain(async (x) => Just(x + 1)).run()
Promise {<resolved>: Just(6)}
Promise {<resolved>: Just(6)}
orDefault
MaybeAsync a ~> a -> IO a(defaultValue: T): Promise<T>
Returns the default value if `this` is `Nothing`, otherwise it returns a Promise that will resolve to the value inside `this`.
ap
<U>(maybeF: PromiseLike<Maybe<(value: T) => U>>): MaybeAsync<Awaited<U>>
Runs an effect if `this` is `Nothing`, returns `this` to make chaining other methods possible.
alt
MaybeAsync a ~> MaybeAsync a -> MaybeAsync a(other: MaybeAsync<T>): MaybeAsync<T>
Returns the first `Just` between the future value of `this` and another future `Maybe` or future `Nothing` if both `this` and the argument are `Nothing`.
extend
MaybeAsync a ~> (MaybeAsync a -> b) -> MaybeAsync b<U>(f: (value: MaybeAsync<T>) => U): MaybeAsync<Awaited<U>>
Returns `this` if it resolves to `Nothing`, otherwise it returns the result of applying the function argument to the value of `this` and wrapping it in a `Just`.
filter
MaybeAsync a ~> (a -> Bool) -> MaybeAsync a(pred: (value: T) => boolean): MaybeAsync<T>
Takes a predicate function and returns `this` if the predicate, applied to the resolved value, is true or Nothing if it's false.
join
MaybeAsync (Maybe a) ~> MaybeAsync a<U>(this: MaybeAsync<Maybe<U>>): MaybeAsync<U>
Flattens a `Maybe` nested inside a `MaybeAsync`. `m.join()` is equivalent to `m.chain(async x => x)`.
toEitherAsync
MaybeAsync b ~> a -> EitherAsync a b<L>(error: L): EitherAsync<L, T>
Converts `this` to a EitherAsync with a default error value
ifJust
(effect: (value: T) => any): MaybeAsync<T>
Runs an effect if `this` is `Just`, returns `this` to make chaining other methods possible.
MaybeAsync.liftMaybe(Just(5)).ifJust(() => console.log('success'))
MaybeAsync.liftMaybe(Nothing).ifJust(() => console.log('success'))
// success
ifNothing
(effect: (value: T) => any): MaybeAsync<T>
Runs an effect if `this` is `Nothing`, returns `this` to make chaining other methods possible.
MaybeAsync.liftMaybe(Just(5)).ifNothing(() => console.log('failure'))
MaybeAsync.liftMaybe(Nothing).ifNothing(() => console.log('failure'))
// failure
void
(): MaybeAsync<void>
Useful if you are not interested in the result of an operation.
caseOf
<U>(patterns: MaybePatterns<T, U>): Promise<U>
Structural pattern matching for `MaybeAsync` in the form of a function.
MaybeAsync.liftMaybe(Nothing).caseOf({ Just: x => x, Nothing: () => 'failure' })
MaybeAsync.liftMaybe(Just(6)).caseOf({ Nothing: () => 0, Just: x => x + 1 })
MaybeAsync.liftMaybe(Nothing).caseOf({ _: () => 0 }) // wildcard
Promise {<resolved>: 'failure'}
Promise {<resolved>: 7}
Promise {<resolved>: 0}
finally
(effect: () => any): MaybeAsync<T>
Similar to the Promise method of the same name, the provided function is called when the `MaybeAsync` is executed regardless of whether the `Maybe` result is `Nothing` or `Just`.

Methods passed to the MaybeAsync async/await callback

liftMaybe
Maybe a -> MaybeAsyncValue a<T>(maybe: Maybe<T>): MaybeAsyncValue<T>
This helper is passed to the function given to the MaybeAsync constructor. It allows you to take a regular Maybe value and lift it to the MaybeAsync context. Awaiting a lifted Maybe will give you the value inside. If the Maybe is Nothing then the function will exit immediately and MaybeAsync will resolve to Nothing after running it.
MaybeAsync(async ({ liftMaybe }) => {
  const value: number = await liftMaybe(Just(5))
}).run()
Promise {<resolved>: Just(5)}
fromPromise
IO (Maybe a) -> MaybeAsyncValue a<T>(promise: PromiseLike<Maybe<T>>): MaybeAsyncValue<T>
This helper is passed to the function given to the MaybeAsync constructor. It allows you to take a Maybe inside a Promise and lift it to the MaybeAsync context. Awaiting a lifted Promise<Maybe> will give you the value inside the Maybe. If the Maybe is Nothing or the Promise is rejected then the function will exit immediately and MaybeAsync will resolve to Nothing after running it.
MaybeAsync(async ({ fromPromise }) => {
  const value: number = await fromPromise(Promise.resolve(Just(5)))
}).run()
Promise {<resolved>: Just(5)}