I'm looking for a client-side JavaScript library that would let me write code similar to what I can do in other languages using some flavour of Option type, for example java.lang.Optional.
My target is to avoid null
/undefined
checks in client code and make API more explicit.
Here is the API I want to be able to write:
var dictionary = {
key1: 'value1',
key2: 'value2'
}
function getValue(key){
var value = dictionary[key];
if(value !== null && value !== undefined)
return Optional.of(value);
else
return Optional.empty();
}
And here is the client code:
var value = getValue('key3');
if(value.isPresent())
console.log('got a value: ' + value.get().toUpperCase());
else
console.log('no value found!');
Or sometimes:
var value = getValue('unknown key').orElse('default value');
If I call get()
on Optional.empty()
value, it should throw some kind of Error
.
If I call Optional.of()
on null
or undefined
, it should throw as well.
I'm looking for a client-side JavaScript library that would let me write code similar to what I can do in other languages using some flavour of Option type, for example java.lang.Optional.
My target is to avoid null
/undefined
checks in client code and make API more explicit.
Here is the API I want to be able to write:
var dictionary = {
key1: 'value1',
key2: 'value2'
}
function getValue(key){
var value = dictionary[key];
if(value !== null && value !== undefined)
return Optional.of(value);
else
return Optional.empty();
}
And here is the client code:
var value = getValue('key3');
if(value.isPresent())
console.log('got a value: ' + value.get().toUpperCase());
else
console.log('no value found!');
Or sometimes:
var value = getValue('unknown key').orElse('default value');
If I call get()
on Optional.empty()
value, it should throw some kind of Error
.
If I call Optional.of()
on null
or undefined
, it should throw as well.
- I feel like options, though sensible, are not really the kind of thing that the Javascript "community" is into. Purescript, on the other hand... – Chris Martin Commented Sep 1, 2015 at 8:17
3 Answers
Reset to default 6Writing an Optional implementation yourself shouldn't be that hard, given the java.lang.Optional source.
Although you have to take some differences into consideration, i.e.Javascript distinguishes between undefined
and null
, you have no such concept as Generics and the Java implementation of map is considered by some as broken. Nevertheless it's fairly easy to implement.
However, I've done an implementation (optional.js) myself, which suits my needs and you may use it if you like, but it probably doesn't satisfy all your requirements (i.e. of()
does not throw an exception on undefined or null, but get()
does)
With my code, your example would be like
function getValue(key){
return Optional.of(dictionary[key]);
}
var value = getValue('key3');
value.ifPresent(function() {
console.log('got a value: ' + value.get().toUpperCase());
});
Btw. code like this
if(value.isPresent())
console.log('got a value: ' + value.get().toUpperCase());
else
console.log('no value found!');
is no valid use case for Optional as you could easily replace it will null/undefined checks. But if you require methods like filter
, map
, flatMap
and ifPresent
, you'll benefit much more for from it.
For those who still interested in this in 2021.
tl;dr:
use the ES Optional chain always. It's more semantic and artistic in the JavaScript way (Maybe with Babel). like:
obj.val?.prop || defaultValue obj.val?.[expr] obj.arr?.[index] obj.func?.(args)
Using the
Optional
class it's may be NOT supported by other packages in TypeScript and if you just want to deeply access an object property is NOT kind of easy.
for more info:
I try to make a roughly Optional
class with TypeScript:
import isNil from "../is/isNil";
// isNil = val => val === null || val === undefined
class Optional<T> {
value = null;
constructor(value: T) {
this.value = value;
}
static EMPTY = new Optional(null);
static empty(): Optional<unknown> {
return Optional.EMPTY;
}
static of<U>(value: U): Optional<U> {
return new Optional(value);
}
isPresent(): boolean {
return !isNil(this.value);
}
filter<T>(predicate: (value: T) => Boolean): Optional<T> {
if (!this.isPresent()) {
return this;
}
return predicate(this.value) ? this : Optional.EMPTY;
}
map<T, U>(mapper: (value: T) => U): Optional<U> {
if (!this.isPresent()) {
return this;
}
return Optional.of(mapper(this.value));
}
flatMap<T, U>(mapper: (value: T) => Optional<U>): Optional<U> {
if (!this.isPresent()) {
return this;
}
const mapped = mapper(this.value);
if (isNil(mapped)) {
throw new Error("flatMap will map the value not to null or undefined.");
}
return mapped;
}
orElse<T>(other: T): T {
return isNil(this.value) ? other : this.value;
}
}
Here you can see when I want to access property by using Optional, it will write like below:
Optional.of(dictionary)
.map((obj)=> {
const descriptors = Object.getOwnPropertyDescriptors(obj)
// ⚠️ when you use like this, it may also cause an error.
return descriptors.key1.value
})
.orElse('defaultValue')
And Using Optional chain
will like this:
dictionary?.key1 || 'defaultValue'
For some usage scenarios, an array with zero to one elements is a pretty good fit.
It is simple, has concise syntax for creation and querying, and also gives you builtin filter() and map(), which are among my main reasons for using Optional:
const one = ["foo"];
const none = [];
console.log("one.isPresent: " + !!one.length);
console.log("none.isPresent: " + !!none.length);
console.log("one.get: " + one[0]);
// Caveat: this 'orElse' will fail for values that are present but falsy:
console.log("one.orElse('fallback'): " + (one[0] || 'fallback') );
console.log("none.orElse('fallback'): " + (none[0] || 'fallback'));
console.log("Fluent chaining: "+ (
none
.filter(s => s.length > 2)
.map(lodash.upperCase)
[0] || "too short"
));
If you are willing to modify the prototype you could add a more robust orElse, and syntactic sugar as desired.