typescript-functional-extensions
Version:
A TypeScript implementation of synchronous and asynchronous Maybe and Result monads
171 lines (170 loc) • 5.45 kB
JavaScript
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;
}
}