@dancrumb/fpish
Version:
FP-friendly classes for Typescript
219 lines (218 loc) • 6.09 kB
JavaScript
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;
}
}