Optional module for JavaScript that provides interfaces to interact with potentially null
/undefined
values.
It aims to take away the effort and boilerplate coding around potentially null
and undefined
which if not properly handled can lead to numerous runtime errors and annoyed users. Null has famously been called a "Billion Dollar Mistake" by Tony Hoare who introduced null
references in ALGOL W back in 1965 because it was so easy to implement. null
references are easy to drop into code, but can quickly add up to numerous errors during program runtime.
The JavaScript Optional package is heavily inspired by the scala.Option library, which really helps programmers deal with values which can be optionally set.
Vanilla JavaScript/TypeScript allows for both null
and undefined
values to exist, meaning that if not properly handled null
errors can occur, e.g. when a property is read and/or function is called on an undefined object. This leads to defensive programming to prevent such issues, e.g.
var result = value ?? 1 * 4;
and
if(value) {
let property = value.property;
let result = value.someFunction();
}
Where we are checking that value
has been set prior to performing any operations with it. This can add considerable boilerplate and ultimately add a lot of protective scaffolding around the code that we actually want to write. The JavaScript Option package intends to remove this scaffolding, allowing us to write code which is safe during runtime.
To install the package from the command line:
npm install @honeycomb-cheesecake/optional@[version]
To install via package.json
:
"@honeycomb-cheesecake/optional": "[version]"
The JavaScript Option package exposes the IOption
interface as well as the ISome
and INone
interfaces that implement.
Interface | Implements | Description |
---|---|---|
IOption |
Interface to interact with potentially null and undefined values. |
|
ISome |
IOption |
Interface for values which are set. |
INone |
IOption |
Interface for values that have not been set. |
To create ISome
and INone
, respective factory methods Some
and None
are available:
import { IOption, None, Some }
const some: IOption<string> = Some<string>("some value");
const none: IOption<number> = None<number>();
It is important to note that ISome
doesn't accept null
and/or undefined
and will throw an error:
import { IOption, Some }
const some: IOption<string[]> = Some<string[]>(["hello", "my", "dear"]); // Okay
Some<number[]>(null); // Throws error.
Some<string[]>(undefined); // Throws error.
In order to simplify the creation of optional values, an Option
factory method is available and is recommended in most cases:
import { IOption, Option }
const some: IOption<string> = Option<string>("some value"); // Returns an ISome<string>.
const none1: IOption<string> = Option<string>(null); // Returns an INone<string>.
const none2: IOption<string> = Option<string>(undefined); // Returns an INone<string>.
Other helper factories have been included to facilitate development:
import { IOption, Option, OptionEmptyString } from "@honeycomb-cheesecake/optional";
const some: IOption<string> = Option(""); // Empty string resolved to ISome<string>.
const none: IOption<string> = OptionEmptyString(""); // Empty string resolved to INone<string>.
some.forEach(value => console.log(value)); // Prints "".
none.forEach(value => console.log(value)); // No op.
import { GetOrElse } from "@honeycomb-cheesecake/optional";
console.error(GetOrElse(10, 100)) // Prints 10.
console.error(GetOrElse(null, 100)) // Prints 100.
console.error(GetOrElse(undefined, 100)) // Prints 100.
import { DoOtherwise } from "@honeycomb-cheesecake/optional";
console.log(DoOtherwise(100, (value: number) => `${value}`, () => "nothing")); // Prints "100".
console.log(DoOtherwise(null, (value: number) => `${value}`, () => "nothing")); // Prints "nothing".
console.log(DoOtherwise(undefined, (value: number) => `${value}`, () => "nothing")); // Prints "nothing".
Returns a unique identifier for the IOption
.
import { IOption, Option } from "@honeycomb-cheesecake/optional";
const valueSome: IOption<string> = Option("Some value.");
const valueNone: IOption<string> = Option<string>(null);
const resultSome: string = valueSome.id();
const resultNone: string = valueNone.id();
console.log(resultSome); // Prints id.
console.log(resultNone); // Prints id.
Returns true
if value is set (i.e. of type ISome
), and returns false
if value isn't set (i.e. of type INone
).
import { IOption, Option } from "@honeycomb-cheesecake/optional";
const valueSome: IOption<string> = Option("Some value.");
const valueNone: IOption<string> = Option<string>(null);
const resultSome: boolean = valueSome.isEmpty();
const resultNone: boolean = valueNone.isEmpty();
console.log(resultSome); // Prints 'false'.
console.log(resultNone); // Prints 'true'.
Returns false
if value is set (i.e. of type ISome
), and returns true
if value isn't set (i.e. of type INone
).
import { IOption, Option } from "@honeycomb-cheesecake/optional";
const valueSome: IOption<string> = Option("Some value.");
const valueNone: IOption<string> = Option<string>(null);
const resultSome: boolean = valueSome.isDefined();
const resultNone: boolean = valueNone.isDefined();
console.log(resultSome); // Prints 'true'.
console.log(resultNone); // Prints 'false'.
Returns the epoch timestamp number
of when the IOption
value was set.
import { IOption, Option } from "@honeycomb-cheesecake/optional";
const valueSome: IOption<string> = Option("Some value.");
const valueNone: IOption<string> = Option<string>();
const resultSome: number = valueSome.timestamp();
const resultNone: number = valueNone.timestamp();
console.log(resultSome); // Prints timestamp.
console.log(resultNone); // Prints timestamp.
Performs an action on the value if set without returning a value. If not set, this is a no-op.
import { IOption, Option } from "@honeycomb-cheesecake/optional";
const valueSome: IOption<string> = Option("Some value.");
const valueNone: IOption<string> = Option<string>();
valueSome.forEach(value => console.log(value)); // Prints "Some value.".
valueNone.forEach(value => console.log(value)); // No op.
Performs the specified action on the value of set and returns an IOption
containing the result. The action specified needs to return an IOption
which differs from map()
which expects the value.
import { IOption, Option } from "@honeycomb-cheesecake/optional";
const valueSome: IOption<number> = Option(1);
const valueNone: IOption<number> = Option<number>();
const flatMapSome: IOption<number> = valueSome.flatMap(value => Option(value * 10)); // `ISome` containing 10 (notice how the result of the transformation is wrapped in an `Option`).
const flatMapNone: IOption<number> = valueNone.flatMap(value => Option(value * 10)); // `INone` (notice how the result of the transformation is wrapped in an `Option`).
Performs the specified action on the value of set and returns an IOption
containing the result. The action specified needs to return the value which differs from flatMap()
which expects an Option
wrapping the value.
import { IOption, Option } from "@honeycomb-cheesecake/optional";
const valueSome: IOption<number> = Option(1);
const valueNone: IOption<number> = Option<number>();
const mapSome: IOption<number> = valueSome.map(value => value * 10); // `ISome` containing 10.
const mapNone: IOption<number> = valueNone.map(value => value * 10); // `INone`.
Returns the value if ISome
, and returns the specified value if INone
. Think of it as get with a default if not available.
import { IOption, Option } from "@honeycomb-cheesecake/optional";
const valueSome: IOption<string> = Option("Some value.");
const valueNone: IOption<string> = Option<string>();
console.log(valueSome.getOrElse("Otherwise value.")); // Prints "Some value.".
console.log(valueNone.getOrElse("Otherwise value.")); // Prints "Otherwise value.".
Returns the value if ISome
, otherwise throws the specified error if INone
. Think of this as almost like a validator of presence.
import { IOption, Option } from "@honeycomb-cheesecake/optional";
const valueSome: IOption<string> = Option("Some value.");
const valueNone: IOption<string> = Option<string>();
console.log(valueSome.getOrError(new Error("Something went wrong."))); // Prints "Some value.".
console.log(valueNone.getOrError(new Error("Something went wrong."))); // Throws error as is `None`.
Returns the value if ISome
, and returns null
if INone
.
import { IOption, Option } from "@honeycomb-cheesecake/optional";
const valueSome: IOption<string> = Option("Some value.");
const valueNone: IOption<string> = Option<string>();
console.log(valueSome.getOrNull()); // Prints "Some value.".
console.log(valueNone.getOrNull()); // Prints null.
Returns the value if ISome
, and returns undefined
if INone
.
import { IOption, Option } from "@honeycomb-cheesecake/optional";
const valueSome: IOption<string> = Option("Some value.");
const valueNone: IOption<string> = Option<string>();
console.log(valueSome.getOrUndefined()); // Prints "Some value.".
console.log(valueNone.getOrUndefined()); // Prints undefined.
Executes someFunc
on the value if ISome
, and noneFunc
if a INone
.
import { IOption, Option } from "@honeycomb-cheesecake/optional";
const valueSome: IOption<string> = Option("Some value.");
const valueNone: IOption<string> = Option<string>();
console.log(valueSome.doOtherwise((value: number) => `${value}`, () => "nothing")); // Prints "Some value.".
console.log(valueNone.doOtherwise((value: number) => `${value}`, () => "nothing")); // Prints "nothing".