【问题标题】:JavaScript analog of java.lang.Optional?java.lang.Optional 的 JavaScript 模拟?
【发布时间】:2015-09-01 07:17:31
【问题描述】:

我正在寻找一个客户端 JavaScript 库,它可以让我使用某种 Option 类型(例如 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(),它也应该抛出。

【问题讨论】:

标签: javascript java null undefined optional


【解决方案1】:

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

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

但是,我自己完成了一个实现 (optional.js),它适合我的需要,您可以根据需要使用它,但它可能无法满足您的所有要求(即 of() 不会抛出undefined 或 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 的有效用例,因为您可以轻松地将其替换为空/未定义检查。但是如果你需要像filtermapflatMapifPresent这样的方法,你会从中受益更多。

【讨论】:

    【解决方案2】:

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

    tl;博士:

    • 始终使用 ES Optional chain。它在 JavaScript 方式中更具语义性和艺术性(也许是 Babel)。喜欢:

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

    更多信息:

    我尝试使用 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 访问属性时,它会像下面这样写:

    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'
    

    【讨论】:

      【解决方案3】:

      对于某些使用场景,零到一元素的数组非常适合。

      它很简单,创建和查询语法简洁,还提供了内置的 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 和语法糖。

      【讨论】:

        猜你喜欢
        • 2011-05-13
        • 1970-01-01
        • 2011-03-06
        • 2021-08-15
        • 1970-01-01
        • 1970-01-01
        • 2011-06-15
        • 2011-02-01
        • 2014-08-19
        相关资源
        最近更新 更多