JavaScript analog of java.lang.Optional?

JavaScript analog of java.lang.Optional?

本文关键字:Optional lang java analog of JavaScript      更新时间:2023-09-26

我正在寻找一个客户端JavaScript库,它可以让我编写类似于我可以在其他语言中使用一些选项类型的代码,例如java.lang.Optional.

我的目标是在客户端代码中避免null/undefined检查,并使API更明确。

下面是我希望能够编写的API:
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();
}

下面是客户端代码:

var value = getValue('key3');
if(value.isPresent())
     console.log('got a value: ' + value.get().toUpperCase());
else
     console.log('no value found!');

有时:

var value = getValue('unknown key').orElse('default value');

如果我在Optional.empty()值上调用get(),它应该抛出某种Error。如果我在nullundefined上调用Optional.of(),它也应该抛出

考虑到java.lang.Optional源代码,自己编写一个可选实现应该不会那么困难。

虽然你必须考虑到一些差异,即javascript区分undefinednull,但你没有泛型这样的概念,并且Java的map实现被一些人认为是坏的。尽管如此,它还是很容易实现的。

然而,我已经做了一个实现(optional.js)自己,这适合我的需要,你可以使用它,如果你喜欢,但它可能不满足你所有的要求(即of()不抛出一个异常对未定义或null,但get())

在我的代码中,您的示例将像
function getValue(key){
    return Optional.of(dictionary[key]);
}
var value = getValue('key3');
value.ifPresent(function() {
     console.log('got a value: ' + value.get().toUpperCase());
 });

顺便说一句。像这样的代码

if(value.isPresent())
     console.log('got a value: ' + value.get().toUpperCase());
else
     console.log('no value found!');

不是Optional的有效用例,因为您可以很容易地将其替换为null/undefined检查。但是如果您需要filter, map, flatMapifPresent这样的方法,您将从中受益更多。

对于那些在2021年仍然对这个感兴趣的人。

tl;博士:

  • 始终使用ES可选链。它以JavaScript的方式更具语义性和艺术性(可能与Babel一起)。如:

    obj.val?.prop || defaultValue
    obj.val?.[expr]
    obj.arr?.[index]
    obj.func?.(args)
    
  • 使用Optional类,它可能是NOT被TypeScript中的其他包支持,如果你只是想深入访问一个对象属性是NOT有点容易。

查看更多信息:

我试着用TypeScript做一个粗略的Optional类:

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;
  }
}

在这里你可以看到当我想使用Optional访问property时,它会像下面这样写:

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')

和使用Optional chain将像这样:

dictionary?.key1 || 'defaultValue'

对于某些使用场景,包含0到1个元素的数组是非常合适的。

它很简单,用于创建和查询的语法很简洁,并且还提供了内置的filter()和map(),这是我使用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"
    ));

如果您愿意修改原型,您可以添加一个更健壮的orElse,并根据需要添加语法糖。