How to avoid 'cannot read property of undefined' errors?

前端 未结 16 2215
野性不改
野性不改 2020-11-22 06:02

In my code, I deal with an array that has some entries with many objects nested inside one another, where as some do not. It looks something like the following:



        
相关标签:
16条回答
  • 2020-11-22 06:21

    Update:

    • If you use JavaScript according to ECMAScript 2020 or later, see optional chaining.
    • TypeScript has added support for optional chaining in version 3.7.
    // use it like this
    obj?.a?.lot?.of?.properties
    

    Solution for JavaScript before ECMASCript 2020 or TypeScript older than version 3.7:

    A quick workaround is using a try/catch helper function with ES6 arrow function:

    function getSafe(fn, defaultVal) {
        try {
            return fn();
        } catch (e) {
            return defaultVal;
        }
    }
    
    // use it like this
    getSafe(() => obj.a.lot.of.properties);
    
    // or add an optional default value
    getSafe(() => obj.a.lot.of.properties, 'nothing');
    

    Working snippet:

    function getSafe(fn, defaultVal) {
      try {
        return fn();
      } catch (e) {
        return defaultVal;
      }
    }
    
    // use it like this
    console.log(getSafe(() => obj.a.lot.of.properties));
    
    // or add an optional default value
    console.log(getSafe(() => obj.a.lot.of.properties, 'nothing'));

    See this article for details.

    0 讨论(0)
  • 2020-11-22 06:21

    What you are doing raises an exception (and rightfully so).

    You can always do

    try{
       window.a.b.c
    }catch(e){
       console.log("YO",e)
    }
    

    But I wouldn't, instead think of your use case.

    Why are you accessing data, 6 levels nested that you are unfamiliar of? What use case justifies this?

    Usually, you'd like to actually validate what sort of object you're dealing with.

    Also, on a side note you should not use statements like if(a.b) because it will return false if a.b is 0 or even if it is "0". Instead check if a.b !== undefined

    0 讨论(0)
  • 2020-11-22 06:21

    If you use Babel, you can already use the optional chaining syntax with @babel/plugin-proposal-optional-chaining Babel plugin. This would allow you to replace this:

    console.log(a && a.b && a.b.c);
    

    with this:

    console.log(a?.b?.c);
    
    0 讨论(0)
  • 2020-11-22 06:21

    I like Cao Shouguang's answer, but I am not fond of passing a function as parameter into the getSafe function each time I do the call. I have modified the getSafe function to accept simple parameters and pure ES5.

    /**
    * Safely get object properties.    
    * @param {*} prop The property of the object to retrieve
    * @param {*} defaultVal The value returned if the property value does not exist
    * @returns If property of object exists it is returned, 
    *          else the default value is returned.
    * @example
    * var myObj = {a : {b : 'c'} };
    * var value;
    * 
    * value = getSafe(myObj.a.b,'No Value'); //returns c 
    * value = getSafe(myObj.a.x,'No Value'); //returns 'No Value'
    * 
    * if (getSafe(myObj.a.x, false)){ 
    *   console.log('Found')
    * } else {
    *  console.log('Not Found') 
    * }; //logs 'Not Found'
    * 
    * if(value = getSafe(myObj.a.b, false)){
    *  console.log('New Value is', value); //logs 'New Value is c'
    * }
    */
    function getSafe(prop, defaultVal) {
      return function(fn, defaultVal) {
        try {
          if (fn() === undefined) {
            return defaultVal;
          } else {
            return fn();
          }
        } catch (e) {
          return defaultVal;
        }
      }(function() {return prop}, defaultVal);
    }
    
    0 讨论(0)
  • 2020-11-22 06:28

    Imagine that we want to apply a series of functions to x if and only if x is non-null:

    if (x !== null) x = a(x);
    if (x !== null) x = b(x);
    if (x !== null) x = c(x);
    

    Now let's say that we need to do the same to y:

    if (y !== null) y = a(y);
    if (y !== null) y = b(y);
    if (y !== null) y = c(y);
    

    And the same to z:

    if (z !== null) z = a(z);
    if (z !== null) z = b(z);
    if (z !== null) z = c(z);
    

    As you can see without a proper abstraction, we'll end up duplicating code over and over again. Such an abstraction already exists: the Maybe monad.

    The Maybe monad holds both a value and a computational context:

    1. The monad keeps the value safe and applies functions to it.
    2. The computational context is a null check before applying a function.

    A naive implementation would look like this:

    ⚠️ This implementation is for illustration purpose only! This is not how it should be done and is wrong at many levels. However this should give you a better idea of what I am talking about.

    As you can see nothing can break:

    1. We apply a series of functions to our value
    2. If at any point, the value becomes null (or undefined) we just don't apply any function anymore.

    const abc = obj =>
      Maybe
        .of(obj)
        .map(o => o.a)
        .map(o => o.b)
        .map(o => o.c)
        .value;
    
    const values = [
      {},
      {a: {}},
      {a: {b: {}}},
      {a: {b: {c: 42}}}
    ];
    
    console.log(
    
      values.map(abc)
    
    );
    <script>
    function Maybe(x) {
      this.value = x; //-> container for our value
    }
    
    Maybe.of = x => new Maybe(x);
    
    Maybe.prototype.map = function (fn) {
      if (this.value == null) { //-> computational context
        return this;
      }
      return Maybe.of(fn(this.value));
    };
    </script>


    Appendix 1

    I cannot explain what monads are as this is not the purpose of this post and there are people out there better at this than I am. However as Eric Elliot said in hist blog post JavaScript Monads Made Simple:

    Regardless of your skill level or understanding of category theory, using monads makes your code easier to work with. Failing to take advantage of monads may make your code harder to work with (e.g., callback hell, nested conditional branches, more verbosity).


    Appendix 2

    Here's how I'd solve your issue using the Maybe monad from monetjs

    const prop = key => obj => Maybe.fromNull(obj[key]);
    
    const abc = obj =>
      Maybe
        .fromNull(obj)
        .flatMap(prop('a'))
        .flatMap(prop('b'))
        .flatMap(prop('c'))
        .orSome('                                                                    
    0 讨论(0)
  • 2020-11-22 06:29

    If I am understanding your question correctly, you want the safest way to determine if an object contains a property.

    The easiest way is to use the in operator.

    window.a = "aString";
    //window should have 'a' property
    //lets test if it exists
    if ("a" in window){
        //true
     }
    
    if ("b" in window){
         //false
     }
    

    Of course you can nest this as deep as you want

    if ("a" in window.b.c) { }
    

    Not sure if this helps.

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