最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to handle calling functions on data that may be undefined? - Stack Overflow

programmeradmin3浏览0评论

I primarily work with React and often find that when I write a function that relies on a ponent's state, I have to perform a check to see if the piece of state is defined before performing any actions.

For example: I have a function that uses .map() to loop over an array of objects fetched from a database and generates jsx for each object in the array. This function is called in the render() function of my ponent. The first time render() is called, the initial array is empty. This results in an error because, of course, the first index of the array is undefined.

I have been circumventing this by making a conditional check to see if the value of the array is undefined or not. This process of writing an if statement each time feels a little clumsy and I was wondering if there is a better way to perform this check or a way to avoid it entirely.

I primarily work with React and often find that when I write a function that relies on a ponent's state, I have to perform a check to see if the piece of state is defined before performing any actions.

For example: I have a function that uses .map() to loop over an array of objects fetched from a database and generates jsx for each object in the array. This function is called in the render() function of my ponent. The first time render() is called, the initial array is empty. This results in an error because, of course, the first index of the array is undefined.

I have been circumventing this by making a conditional check to see if the value of the array is undefined or not. This process of writing an if statement each time feels a little clumsy and I was wondering if there is a better way to perform this check or a way to avoid it entirely.

Share Improve this question asked Oct 3, 2018 at 18:30 Chris VossChris Voss 7982 gold badges11 silver badges23 bronze badges 1
  • Possible duplicate of How to avoid 'cannot read property of undefined' errors? – jo_va Commented Feb 20, 2019 at 10:12
Add a ment  | 

2 Answers 2

Reset to default 8

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 mented by devserkan):

(arr || []).map()

As per your ment:

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

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

  1. 暂无评论