最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

JavaScript analog of java.lang.Optional? - Stack Overflow

programmeradmin1浏览0评论

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.

Share Improve this question edited Sep 1, 2015 at 8:08 tsayen asked Sep 1, 2015 at 7:17 tsayentsayen 3,0553 gold badges19 silver badges22 bronze badges 1
  • 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
Add a comment  | 

3 Answers 3

Reset to default 6

Writing 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.

发布评论

评论列表(0)

  1. 暂无评论