UNPKG

typescript-functional-extensions

Version:

A TypeScript implementation of synchronous and asynchronous Maybe and Result monads

171 lines (170 loc) 5.45 kB
import { Maybe } from './maybe.js'; import { ResultAsync } from './resultAsync.js'; import { Unit } from './unit.js'; import { isDefined, isFunction, isPromise, pipeFromArray, } from './utilities.js'; /** * Represents an asynchronous value that might or might not exist */ export class MaybeAsync { static from(valueOrPromiseOrMaybePromise) { if (isPromise(valueOrPromiseOrMaybePromise)) { return new MaybeAsync(valueOrPromiseOrMaybePromise.then((v) => v instanceof Maybe ? v : Maybe.from(v))); } else if (valueOrPromiseOrMaybePromise instanceof Maybe) { return new MaybeAsync(Promise.resolve(valueOrPromiseOrMaybePromise)); } throw new Error('Value must be a Promise or Maybe'); } /** * Creates a new MaybeAsync from the given value * @param value * @returns */ static some(value) { return new MaybeAsync(Promise.resolve(Maybe.some(value))); } /** * Creates a new MaybeAsync with no value * @returns */ static none() { return new MaybeAsync(Promise.resolve(Maybe.none())); } get hasValue() { return this.value.then((m) => m.hasValue); } get hasNoValue() { return this.value.then((m) => m.hasNoValue); } value; constructor(value) { this.value = value; } getValueOrDefault(defaultValueOrFactory) { if (isDefined(this.value)) { return this.value.then((m) => { if (isFunction(defaultValueOrFactory)) { return m.getValueOrDefault(defaultValueOrFactory()); } else { return m.getValueOrDefault(defaultValueOrFactory); } }); } if (isFunction(defaultValueOrFactory)) { return Promise.resolve(defaultValueOrFactory()); } return Promise.resolve(defaultValueOrFactory); } /** * Returns the value of the MaybeAsync and throws * and returns a rejected Promise is there is none * @returns */ getValueOrThrow() { if (isDefined(this.value)) { return this.value.then((m) => { return m.getValueOrThrow(); }); } return Promise.reject('No value'); } /** * Executes the given operator functions, creating a custom pipeline * @param operations MaybeAsync operation functions * @returns */ pipe(...operations) { return pipeFromArray(operations)(this); } map(projection) { return MaybeAsync.from(this.value.then(async (m) => { if (m.hasNoValue) { return Maybe.none(); } const result = projection(m.getValueOrThrow()); if (isPromise(result)) { return result.then((r) => Maybe.some(r)); } return Maybe.some(result); })); } tap(action) { return new MaybeAsync(this.value.then(async (m) => { if (m.hasNoValue) { return m; } const result = action(m.getValueOrThrow()); if (isPromise(result)) { await result; } return m; })); } bind(projection) { return new MaybeAsync(this.value.then((m) => { if (m.hasNoValue) { return Maybe.none(); } const result = projection(m.getValueOrThrow()); if (result instanceof Maybe) { return result; } return result.toPromise(); })); } match(matcher) { return this.value.then((m) => m.match(matcher)); } execute(func) { return this.value.then((m) => { if (m.hasNoValue) { return Unit.Instance; } const result = func(m.getValueOrThrow()); return isPromise(result) ? result.then(() => Unit.Instance) : Unit.Instance; }); } or(fallback) { return new MaybeAsync(this.value.then((m) => { if (fallback instanceof MaybeAsync) { return m.orAsync(fallback).toPromise(); } else { if (m.hasValue) { return m; } if (!isFunction(fallback)) { if (fallback instanceof Maybe) { return m.or(fallback); } return m.or(fallback); } const result = fallback(); if (result instanceof MaybeAsync) { return m.orAsync(result).toPromise(); } if (result instanceof Maybe) { return m.or(result); } return m.or(result); } })); } toResult(error) { return ResultAsync.from(this.value.then((m) => m.toResult(error))); } /** * Returns the inner Promise, wrapping Maybe.none if handleError is true * for a rejected Promise, otherwise rejected Promise handling is left up to the caller. * @param handleError * @returns */ toPromise(handleError = false) { return isDefined(handleError) && handleError ? this.value.catch((_) => Maybe.none()) : this.value; } }