How to handle calling functions on data that may be undefined?

后端 未结 2 477
情书的邮戳
情书的邮戳 2020-11-28 16:02

I primarily work with React and often find that when I write a function that relies on a component\'s state, I have to perform a check to see if the piece of state is define

2条回答
  •  感动是毒
    2020-11-28 16:47

    What you actually need here is called optional chaining:

    obj?.a?.b?.c // no error if a, b, or c don't exist or are undefined/null
    

    The ?. is the existential operator and it allows you to access properties safely and won't throw if the property is missing. However optional chaining is not yet part of JavaScript but has been proposed and is in stage 3, see State 3 of TC39.

    But, using proxies and a Maybe class, you can implement optional chaining and return a default value when the chain fails.

    A wrap() function is used to wrap objects on which you want to apply optional chaining. Internally, wrap creates a Proxy around your object and manages missing values using a Maybe wrapper.

    At the end of the chain, you unwrap the value by chaining getOrElse(default) with a default value which is returned when the chain is not valid:

    const obj = {
      a: 1,
      b: {
        c: [4, 1, 2]
      },
      c: () => 'yes'
    };
    
    console.log(wrap(obj).a.getOrElse(null)) // returns 1
    console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null)) // returns null
    console.log(wrap(obj).b.c.getOrElse([])) // returns [4, 1, 2]
    console.log(wrap(obj).b.c[0].getOrElse(null)) // returns 4
    console.log(wrap(obj).b.c[100].getOrElse(-1)) // returns -1
    console.log(wrap(obj).c.getOrElse(() => 'no')()) // returns 'yes'
    console.log(wrap(obj).d.getOrElse(() => 'no')()) // returns 'no'
    
    wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
    wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2
    

    The complete example:

    class Maybe {
      constructor(value) {
        this.__value = value;
      }
      static of(value){
        if (value instanceof Maybe) return value;
        return new Maybe(value);
      }
      getOrElse(elseVal) {
        return this.isNothing() ? elseVal : this.__value;
      }
      isNothing() {
        return this.__value === null || this.__value === undefined;
      }
      map(fn) {  
        return this.isNothing()
          ? Maybe.of(null)
          : Maybe.of(fn(this.__value));
      }
    }
    
    function wrap(obj) {
      function fix(object, property) {
        const value = object[property];
        return typeof value === 'function' ? value.bind(object) : value;
      }
      return new Proxy(Maybe.of(obj), {
        get: function(target, property) {
          if (property in target) {
              return fix(target, property);
          } else {
            return wrap(target.map(val => fix(val, property)));
          }
        }
      });
    }
    
    const obj = { a: 1, b: { c: [4, 1, 2] }, c: () => 'yes' };
    
    console.log(wrap(obj).a.getOrElse(null))
    console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null))
    console.log(wrap(obj).b.c.getOrElse([]))
    console.log(wrap(obj).b.c[0].getOrElse(null))
    console.log(wrap(obj).b.c[100].getOrElse(-1))
    console.log(wrap(obj).c.getOrElse(() => 'no')())
    console.log(wrap(obj).d.getOrElse(() => 'no')())
    
    wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
    wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2

提交回复
热议问题