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

后端 未结 2 457
情书的邮戳
情书的邮戳 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:45

    Check the array before using map:

    arr && arr.map()
    

    OR,

    arr && arr.length && arr.map() // if you want to map only if not empty array
    

    OR,

    We can even use like this (as commented by devserkan):

    (arr || []).map()
    

    As per your comment:

    I wish there was a safe navigation operator like with C# (arr?.map())

    Yes, obviously. This is called optional chaining in JavaScript which is still in proposal. If it is accepted, then you may use like this:

    arr?.map()
    

    You can see it in staging 1 for which you may use babel preset stage1


    But obviously, except the checking array length, your requirement will not be fulfilled:

    This results in an error because, of course, the first index of the array is undefined.

    So, I suggest you to use:

    arr && arr.length && arr.map()
    
    0 讨论(0)
  • 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

    0 讨论(0)
提交回复
热议问题