UNPKG

@dancrumb/fpish

Version:

FP-friendly classes for Typescript

219 lines (218 loc) 6.09 kB
import { NoSuchElementException } from './exceptions/NoSuchElementException.js'; /* * TS Implementation of https://docs.oracle.com/javase/8/docs/api/java/util/fp/Optional.html */ const haveValue = (value) => typeof value !== 'undefined'; const strictEquals = (a, b) => a === b; /** * An implementation of https://docs.oracle.com/javase/8/docs/api/java/util/fp/Optional.html */ export class Optional { value; constructor(val) { if (val !== null) { this.value = val; } } /** * Create an empty Optional instance with type `ET` */ static empty() { return new Optional(); } /** * Create a non-empty Optional instance with type `T` * @param value */ static of(value) { if (typeof value === 'undefined' || value === null) { return Optional.empty(); } if (value instanceof Optional) { if (!value.isPresent()) { return Optional.empty(); } const val = value.get(); return new Optional(val); } return new Optional(value); } /** * Checks the value of the optional. If it matches the `predicate`, then a non-empty * Optional of the same value will be returned. * * Otherwise, an empty Optional is returned. * * @param predicate */ filter(predicate) { if (haveValue(this.value) && predicate(this.value)) { return new Optional(this.value); } return Optional.empty(); } map(mapper) { if (haveValue(this.value)) { const result = mapper(this.value); if (typeof result !== 'undefined' && result !== null) { return Optional.of(result); } } return Optional.empty(); } /** * Map the value of an Optional to an Optional value of the same or a different type. * * This differs from `map`, such that the returned Optional is 'squashed' so that * the returned value is `Optional<U>` not `Optional<Optional<U>>` * * @param mapper */ flatMap(mapper) { if (haveValue(this.value)) { return mapper(this.value); } return Optional.empty(); } /** * Get the value of the optional or throw a `NoSuchElement` exception */ get() { if (haveValue(this.value)) { return this.value; } throw new NoSuchElementException(); } /** * If the Optional is non-empty, pass the value to a consuming function. * * The `consumer` should not return a value. If it does, this value is ignored. * * @param consumer */ ifPresent(consumer) { if (haveValue(this.value)) { consumer(this.value); return { orElse: () => { }, }; } return { orElse: (f) => f(), }; } /** * Check whether the Optional is empty or not. */ isPresent() { return haveValue(this.value); } /** * Return the Optional's value if it is not empty, otherwise return the * provided value. * * @param other */ orElse(other) { if (haveValue(this.value)) { return this.value; } return other; } /** * Return the Optional's value if it is not empty, otherwise return undefined. */ orNothing() { if (haveValue(this.value)) { return this.value; } return undefined; } /** * Return the Optional's value if it is not empty, otherwise return null. */ orNull() { if (haveValue(this.value)) { return this.value; } return null; } /** * Return the Optional's value if it is not empty, otherwise call `other` and * return that value. * * @param other */ orElseGet(other) { if (haveValue(this.value)) { return this.value; } return other(); } /** * Return the Optional's value if it is not empty, otherwise call `exceptionSupplier` * to generate an exception that is then thrown * * @param exceptionSupplier */ orElseThrow(exceptionSupplier) { if (haveValue(this.value)) { return this.value; } throw exceptionSupplier(); } /** * Compares this Optional value to the provided on. * * Empty Optionals are never considered equal to anything, including other * empty Optionals. * * @param val * @param isEqual - an optional function for comparing equality */ equals(val, isEqual = strictEquals) { if (this.isPresent() && val.isPresent()) { return isEqual(val.get(), this.get()); } return false; } /** * Converts the internal type to another. `guard` is called to do the necessary * type checking. This is generally used for up- and down-casting. * * @param guard * * @deprecated just use `filter` with a type guard */ cast(guard) { if (this.isPresent() && guard(this.get())) { return Optional.of(this.get()); } return Optional.empty(); } /** * Returns a property of the enclosed type. Obviously, this assumes * that the contained type has properties. * * Accepts an optional default value * * @param key a property of the enclosed value * @param orElse a default value */ property(key, orElse) { const value = this.map((u) => u[key]); if (typeof orElse !== 'undefined') { return value.orElse(orElse); } return value.orNothing(); } /** * See [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description) */ toJSON() { if (haveValue(this.value)) { return this.value; } return undefined; } }