'Tanımlanmamış özelliklerin okunamaması' hatalarından nasıl kaçınılır?


119

Kodumda, birçok nesnenin iç içe geçmiş olduğu, bazılarının bulunmadığı bazı girdileri olan bir dizi ile ilgileniyorum. Aşağıdaki gibi bir şeye benziyor:

// where this array is hundreds of entries long, with a mix
// of the two examples given
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];

Bu bana sorun yaratıyor çünkü zaman zaman dizi boyunca yinelemem gerekiyor ve tutarsızlık bana şu gibi hatalar veriyor:

for (i=0; i<test.length; i++) {
    // ok on i==0, but 'cannot read property of undefined' on i==1
    console.log(a.b.c);
}

Söyleyebileceğimin farkındayım if(a.b){ console.log(a.b.c)}, ancak iç içe geçmiş 5 veya 6 nesnenin olduğu durumlarda bu son derece sıkıcı. YALNIZCA console.log varsa, ancak bir hata atmadan yapabilmemin başka (daha kolay) bir yolu var mı?


3
Hata muhtemelen normal bir Javascript istisnasıdır, bu nedenle try..catchifadeyi deneyin . Bununla birlikte, çılgınca heterojen öğeler içeren bir dizi bana bir tasarım sorunu gibi görünüyor.
millimoose

3
Yapınız öğeler arasında tutarlı değilse, varlığını kontrol etmenin nesi yanlış? Gerçekten kullanırdım if ("b" in a && "c" in a.b). "Sıkıcı" olabilir, ama tutarsızlık için elde ettiğiniz şey budur ... normal mantık.
Ian

2
Var olmayan mülklere neden erişiyorsunuz, neden nesnelerin nasıl göründüğünü bilmiyorsunuz?
Bergi

9
Birisinin her şeyi çökertmek için neden bir hata istemediğini anlayabiliyorum. Bir nesnenin özelliklerinin var olup olmadığına her zaman güvenemezsiniz. Nesnenin hatalı biçimlendirilmesi olayını işleyebilecek bir şeyiniz varsa, kodunuz çok daha verimli ve daha az kırılgandır.
SSH Bu

3
Gerçek yaşam koşullarında kaç tane nesnenin / dizinin hatalı biçimlendirildiğine
şaşıracaksınız

Yanıtlar:


122

Güncelleme :

  • JavaScript'i ECMAScript 2020 veya sonrasına göre kullanıyorsanız, isteğe bağlı zincirlemeye bakın .
  • TypeScript, 3.7 sürümünde isteğe bağlı zincirleme desteği ekledi .
// use it like this
obj?.a?.lot?.of?.properties

ECMASCript 2020'den önceki JavaScript veya sürüm 3.7'den daha eski TypeScript için çözüm :

Hızlı bir çözüm, ES6 ok işlevine sahip bir dene / yakala yardımcı işlevi kullanıyor :

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');

Çalışma pasajı:

Ayrıntılar için bu makaleye bakın.


2
Onu seviyorum! Ekleyeceğim tek şey, yakalamanın içindeki bir console.warn, böylece hatayı biliyorsunuz ama devam ediyor.
Haham Shuki Gur

Tüm istisnaları yeniden atmadan yakalamak kötüdür ve genel olarak beklenen yürütme akışının bir parçası olarak istisnaları kullanmak da pek iyi değildir - bu durumda oldukça iyi kapsansa da.
hugo

50

Yaptığınız şey bir istisna yaratıyor (ve haklı olarak öyle).

Her zaman yapabilirsin

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

Ama kullanım durumunuzu düşünmüyorum.

Tanımadığınız 6 seviye iç içe geçmiş şekilde neden verilere erişiyorsunuz? Hangi kullanım durumu bunu haklı çıkarır?

Genellikle, ne tür bir nesneyle uğraştığınızı gerçekten doğrulamak istersiniz.

Ayrıca, bir yan notta if(a.b), ab 0 ise veya "0" olsa bile false döndüreceği için gibi ifadeler kullanmamalısınız . Bunun yerine kontrol edina.b !== undefined


1
İlk düzenlemenizle ilgili olarak: Haklı; JSON yapılandırılmış veritabanı girişleriyle uğraşıyorum, öyle ki nesneler birden fazla alan düzeyini ölçeklendirecek (yani, tüm vakaların veri girilmediği girişler.users.messages.date vb.)
Ari

"ab 0 ise doğru dönecektir" - hayır. typeof a.b === "undefined" && a.b!=null- ikinci kısmı birinciden sonra yapmak gereksiz ve sadece yapmak daha mantıklıif ("b" in a)
Ian,

@Ian evet, açıkçası tam tersini kastettim, ab "0" olsa bile yanlış döndürür. İyi yakaladın
Benjamin Gruenbaum

@BenjaminGruenbaum Kulağa hoş geliyor, bunu kastettiğinden emin değildim. Ayrıca, typeof a.b !== "tanımsız" && ab! = Null` istediğinizi düşünüyorum!==
Ian'a

3
Ab && abc && console.log (abc) ile uğraşmak istemiyorsanız, bilinmeyenleri sürekli olarak günlüğe kaydetmenin tek yolu budur.
Brian Cray

14

Sorunuzu doğru anlıyorsam, bir nesnenin bir özellik içerip içermediğini belirlemenin en güvenli yolunu istersiniz.

En kolay yol, in operatörü .

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

if ("b" in window){
     //false
 }

Elbette bunu istediğin kadar derine yerleştirebilirsin

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

Bunun yardımcı olup olmadığından emin değilim.


9
Bunu istediğiniz kadar derine güvenle yerleştiremezsiniz. Ya window.btanımsız ise? Bir tür hatası alacaksınız:Cannot use 'in' operator to search for 'c' in undefined
Trevor

13

Eğer kullanıyorsanız lodash , onların "sahip" işlevini kullanabilirsiniz. Yerel "giriş" e benzer, ancak yollara izin verir.

var testObject = {a: {b: {c: 'walrus'}}};
if(_.has(testObject, 'a.b.c')) {
  //Safely access your walrus here
}

2
En iyisi, _.get()kolay okuma için varsayılan olarak kullanabiliriz :_.get(object, 'a.b.c', 'default');
Ifnot

12

Bunu dene. Eğer a.btanımsız, bu bırakacak ifherhangi istisnasız açıklama.

if (a.b && a.b.c) {
  console.log(a.b.c);
}

5

Bu, derin veya karmaşık json nesnesiyle çalışırken yaygın bir sorundur, bu yüzden kodu okunamaz hale getirecek birden fazla denetimi denemekten / yakalamadan veya yerleştirmekten kaçınmaya çalışıyorum, genellikle bu küçük kod parçasını işi yapmak için tüm süreçlerimde kullanıyorum.

/* ex: getProperty(myObj,'aze.xyz',0) // return myObj.aze.xyz safely
 * accepts array for property names: 
 *     getProperty(myObj,['aze','xyz'],{value: null}) 
 */
function getProperty(obj, props, defaultValue) {
    var res, isvoid = function(x){return typeof x === "undefined" || x === null;}
    if(!isvoid(obj)){
        if(isvoid(props)) props = [];
        if(typeof props  === "string") props = props.trim().split(".");
        if(props.constructor === Array){
            res = props.length>1 ? getProperty(obj[props.shift()],props,defaultValue) : obj[props[0]];
        }
    }
    return typeof res === "undefined" ? defaultValue: res;
}

5

Eğer varsa lodash onun kullanabilirsiniz .getyöntemi

_.get(a, 'b.c.d.e')

veya varsayılan bir değer verin

_.get(a, 'b.c.d.e', default)

4

Kullandığım undefsafe dini. İstediğiniz değeri alana ya da "tanımsız" döndürene kadar her seviyeyi nesnenize doğru test eder. Ama asla hata yapmayın.


2
bu lodash'a benzer_.get
Filype

Güzel ses! Lodash'ın diğer özelliklerine ihtiyacınız yoksa yine de faydalıdır.
martined


3

Cao Shouguang'ın cevabını beğendim, ancak çağrıyı her yaptığımda getSafe işlevine parametre olarak bir işlev geçirmekten hoşlanmıyorum. Basit parametreleri ve saf ES5'i kabul etmek için getSafe işlevini değiştirdim.

/**
* 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);
}

Gerçekten çalışmıyor getSafe(myObj.x.c). Chrome ve Firefox'un en son sürümleri denendi.
Rickard Elimää

2

Str'nin cevabında, özellik tanımsız ise ayarlanmış varsayılan değer yerine 'tanımsız' değeri döndürülecektir. Bu bazen hatalara neden olabilir. Aşağıdakiler, özellik veya nesne tanımsız olduğunda defaultVal'in her zaman döndürülmesini sağlayacaktır.

const temp = {};
console.log(getSafe(()=>temp.prop, '0'));

function getSafe(fn, defaultVal) {
    try {
        if (fn() === undefined) {
            return defaultVal
        } else {
            return fn();
        }

    } catch (e) {
        return defaultVal;
    }
}

Hardy Le Roux'un kodumun geliştirilmiş sürümü, benimki çalışırken, let myObj = {} getSafe (() => myObj.ab, "nice") ile çalışmıyor. Nedenini açıklayan var mı?
Cao shouguang

2

Lodash, getaşağıda gösterildiği gibi isteğe bağlı üçüncü bir parametre olarak varsayılana izin veren bir yönteme sahiptir:

const myObject = {
  has: 'some',
  missing: {
    vars: true
  }
}
const path = 'missing.const.value';
const myValue = _.get(myObject, path, 'default');
console.log(myValue) // prints out default, which is specified above
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>


2

Boş xdeğilse ve ancak xboş değilse bir dizi işlevi uygulamak istediğimizi düşünün :

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

Şimdi de şunu yapmamız gerektiğini söyleyelim y:

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

Ve aynı şey z:

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

Düzgün bir soyutlama olmadan görebileceğiniz gibi, sonunda kodu tekrar tekrar çoğaltacağız. Böyle bir soyutlama zaten var: Belki monad.

Belki monad bir değeri ve bir hesaplama bağlamı hem tutar:

  1. Monad değeri güvende tutar ve ona işlevler uygular.
  2. Hesaplama bağlamı, bir işlevi uygulamadan önce boş bir kontroldür.

Saf bir uygulama şöyle görünür:

⚠️ Bu uygulama yalnızca örnekleme amaçlıdır! Bu nasıl yapılması gerektiği değildir ve birçok düzeyde yanlıştır. Ancak bu, neden bahsettiğim hakkında size daha iyi bir fikir verecektir.

Gördüğünüz gibi hiçbir şey bozulamaz:

  1. Değerimize bir dizi işlev uygularız
  2. Herhangi bir noktada, değer boş (veya tanımsız) olursa, artık herhangi bir işlevi uygulamıyoruz.

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>


ek 1

Monadların ne olduğunu açıklayamam çünkü bu yazının amacı bu değil ve bunda benden daha iyi insanlar var. Ancak Eric Elliot'ın eski blog gönderisinde söylediği gibi JavaScript Monads Made Simple :

Beceri seviyeniz veya kategori teorisi anlayışınız ne olursa olsun, monad kullanmak kodunuzun daha kolay çalışmasını sağlar. Monadlardan yararlanamamak, kodunuzun çalışmasını zorlaştırabilir (örneğin, geri arama cehennemi, iç içe geçmiş koşullu dallar, daha fazla ayrıntı).


Ek 2

İşte kullanarak sorununuzu çözmek istiyorum nasıl Belki :

const prop = key => obj => Maybe.fromNull(obj[key]);

const abc = obj =>
  Maybe
    .fromNull(obj)
    .flatMap(prop('a'))
    .flatMap(prop('b'))
    .flatMap(prop('c'))
    .orSome('🌯')
    
const values = [
  {},
  {a: {}},
  {a: {b: {}}},
  {a: {b: {c: 42}}}
];

console.log(

  values.map(abc)

);
<script src="https://www.unpkg.com/monet@0.9.0/dist/monet.js"></script>
<script>const {Maybe} = Monet;</script>


0

Bunu daha önce cevapladım ve bugün benzer bir kontrol yapıyordum. Yuvalanmış noktalı bir özelliğin var olup olmadığını kontrol etmek için bir basitleştirme. Değeri döndürmek için bunu veya hedefinize ulaşmak için bazı varsayılanları değiştirebilirsiniz.

function containsProperty(instance, propertyName) {
    // make an array of properties to walk through because propertyName can be nested
    // ex "test.test2.test.test"
    let walkArr = propertyName.indexOf('.') > 0 ? propertyName.split('.') : [propertyName];

    // walk the tree - if any property does not exist then return false
    for (let treeDepth = 0, maxDepth = walkArr.length; treeDepth < maxDepth; treeDepth++) {

        // property does not exist
        if (!Object.prototype.hasOwnProperty.call(instance, walkArr[treeDepth])) {
            return false;
        }

        // does it exist - reassign the leaf
        instance = instance[walkArr[treeDepth]];

    }

    // default
    return true;

}

Sorunuzda şöyle bir şey yapabilirsiniz:

let test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
containsProperty(test[0], 'a.b.c');

0

Genelde şu şekilde kullanırım:

 var x = object.any ? object.any.a : 'def';

0

Özelliği almadan önce varsayılan bir değer vererek hata almaktan kurtulabilirsiniz.

var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];

for (i=0; i<test.length; i++) {
    const obj = test[i]
    // No error, just undefined, which is ok
    console.log(((obj.a || {}).b || {}).c);
}

Bu, dizilerde de harika çalışıyor:

const entries = [{id: 1, name: 'Scarllet'}]
// Giving a default name when is empty
const name = (entries.find(v => v.id === 100) || []).name || 'no-name'
console.log(name)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.