Bagaimana cara menghindari kesalahan 'tidak dapat membaca properti tak terdefinisi'?

118

Dalam kode saya, saya berurusan dengan array yang memiliki beberapa entri dengan banyak objek bersarang di dalam satu sama lain, sedangkan beberapa tidak. Ini terlihat seperti berikut:

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

Ini memberi saya masalah karena saya kadang-kadang perlu mengulang melalui array, dan ketidakkonsistenan membuat saya mengalami kesalahan seperti:

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

Saya sadar bahwa saya dapat mengatakannya if(a.b){ console.log(a.b.c)}, tetapi ini sangat membosankan jika terdapat hingga 5 atau 6 objek yang bersarang satu sama lain. Apakah ada cara lain (lebih mudah) yang saya dapat memilikinya HANYA melakukan console.log jika ada, tetapi tanpa menimbulkan kesalahan?

Ari
sumber
3
Kesalahannya mungkin pengecualian Javascript biasa, jadi coba try..catchpernyataannya. Meskipun demikian, array yang berisi elemen heterogen yang liar terlihat seperti masalah desain bagi saya.
millimoose
3
Jika struktur Anda tidak konsisten di seluruh item, lalu apa yang salah dengan memeriksa keberadaan? Sungguh, saya akan menggunakan if ("b" in a && "c" in a.b). Ini mungkin "membosankan", tapi itulah yang Anda dapatkan dari ketidakkonsistenan ... logika normal.
Ian
2
Mengapa Anda mengakses properti yang tidak ada, mengapa Anda tidak tahu bagaimana tampilan objek?
Bergi
9
Saya bisa mengerti mengapa seseorang tidak ingin kesalahan menghancurkan segalanya. Anda tidak bisa selalu mengandalkan properti objek untuk ada atau tidak ada. Jika Anda memiliki sesuatu di tempat yang dapat menangani kejadian di mana objek tersebut cacat, maka kode Anda jauh lebih efisien dan tidak terlalu rapuh.
SSH
3
Anda akan terkejut melihat berapa banyak objek / array yang salah
format dalam

Jawaban:

121

Pembaruan :

  • Jika Anda menggunakan JavaScript menurut ECMAScript 2020 atau yang lebih baru, lihat rangkaian opsional .
  • TypeScript telah menambahkan dukungan untuk rangkaian opsional di versi 3.7 .
// use it like this
obj?.a?.lot?.of?.properties

Solusi untuk JavaScript sebelum ECMASCript 2020 atau TypeScript lebih lama dari versi 3.7 :

Solusi cepat adalah menggunakan fungsi pembantu coba / tangkap dengan fungsi panah ES6 :

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

Cuplikan kerja:

Lihat artikel ini untuk detailnya.

str
sumber
2
Aku menyukainya! satu-satunya hal yang akan saya tambahkan adalah console.warn di dalam tangkapan, sehingga Anda mengetahui kesalahannya tetapi terus berlanjut.
Rabbi Shuki Gur
Menangkap semua pengecualian tanpa melempar ulang itu buruk , dan umumnya menggunakan pengecualian sebagai bagian dari aliran eksekusi yang diharapkan juga tidak bagus - meskipun dalam kasus ini cukup baik.
hugo
50

Apa yang Anda lakukan menimbulkan pengecualian (dan memang seharusnya demikian).

Anda selalu bisa melakukannya

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

Tapi saya tidak akan, sebaliknya memikirkan kasus penggunaan Anda.

Mengapa Anda mengakses data, 6 level bersarang yang tidak Anda kenal? Kasus penggunaan apa yang membenarkan hal ini?

Biasanya, Anda ingin benar-benar memvalidasi jenis objek yang Anda hadapi.

Selain itu, di samping catatan Anda tidak boleh menggunakan pernyataan seperti if(a.b)karena akan mengembalikan false jika ab adalah 0 atau bahkan jika itu adalah "0". Sebaliknya, periksa apakaha.b !== undefined

Benjamin Gruenbaum
sumber
1
Sehubungan dengan suntingan pertama Anda: Ini dibenarkan; Saya berurusan dengan entri database terstruktur JSON, sehingga objek akan menskalakan beberapa level bidang (mis. Entries.users.messages.date dll., Di mana tidak semua kasus memiliki data yang dimasukkan)
Ari
"itu akan mengembalikan nilai true jika ab adalah 0" - tidak. typeof a.b === "undefined" && a.b!=null- tidak perlu melakukan bagian kedua setelah yang pertama, dan lebih masuk akal untuk melakukannyaif ("b" in a)
Ian
@Ian ya, saya jelas bermaksud sebaliknya, itu akan mengembalikan false meskipun ab adalah "0". Tangkapan bagus
Benjamin Gruenbaum
@BenjaminGruenbaum Kedengarannya bagus, tidak yakin apakah Anda bersungguh-sungguh. Juga, saya pikir Anda ingin typeof a.b !== "undefined" && ab! = Null` - perhatikan!==
Ian
3
Jika Anda tidak ingin bosan dengan ab && abc && console.log (abc) maka ini adalah satu-satunya cara untuk mencatat hal-hal yang tidak diketahui secara konsisten.
Brian Cray
14

Jika saya memahami pertanyaan Anda dengan benar, Anda menginginkan cara teraman untuk menentukan apakah suatu objek mengandung properti.

Cara termudah adalah dengan menggunakan inoperator .

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

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

Tentu saja Anda bisa mengumpulkan ini sedalam yang Anda inginkan

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

Tidak yakin apakah ini membantu.

matt weiss
sumber
9
Anda tidak dapat mengumpulkan ini dengan aman sedalam yang Anda inginkan. Bagaimana jika window.btidak ditentukan? Anda akan mendapatkan kesalahan tipe:Cannot use 'in' operator to search for 'c' in undefined
Trevor
13

Jika Anda menggunakan lodash , Anda dapat menggunakan fungsi "has" mereka. Ini mirip dengan "dalam" asli, tetapi memungkinkan jalur.

var testObject = {a: {b: {c: 'walrus'}}};
if(_.has(testObject, 'a.b.c')) {
  //Safely access your walrus here
}
tehwalris.dll
sumber
2
Terbaik, kita dapat menggunakan _.get()dengan default agar mudah dibaca:_.get(object, 'a.b.c', 'default');
Ifnot
12

Coba ini. Jika a.btidak ditentukan, itu akan meninggalkan ifpernyataan tanpa terkecuali.

if (a.b && a.b.c) {
  console.log(a.b.c);
}
Sean Chen
sumber
5

Ini adalah masalah umum ketika bekerja dengan objek json yang dalam atau kompleks, jadi saya mencoba untuk menghindari mencoba / menangkap atau menyematkan beberapa pemeriksaan yang akan membuat kode tidak dapat dibaca, saya biasanya menggunakan potongan kode kecil ini di semua proses saya untuk melakukan pekerjaan itu.

/* 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;
}
Maelkhor
sumber
5

Jika Anda memiliki lodash, Anda dapat menggunakan .getmetodenya

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

atau berikan nilai default

_.get(a, 'b.c.d.e', default)
Brandon Dyer
sumber
4

Saya menggunakan undefsafe secara religius. Ini menguji setiap level ke dalam objek Anda sampai mendapatkan nilai yang Anda minta, atau mengembalikan "tidak ditentukan". Tapi tidak pernah ada kesalahan.

martinedwards
sumber
2
yang mirip dengan lodash_.get
Filype
Teriakan yang bagus! Masih berguna jika Anda tidak membutuhkan fitur lodash lainnya.
Mulai berlaku
3

Saya suka jawaban Cao Shouguang, tetapi saya tidak suka melewatkan fungsi sebagai parameter ke dalam fungsi getSafe setiap kali saya melakukan panggilan. Saya telah memodifikasi fungsi getSafe untuk menerima parameter sederhana dan ES5 murni.

/**
* 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);
}
Hardy Le Roux
sumber
Ini tidak benar-benar berfungsi getSafe(myObj.x.c). Sudah mencoba versi terbaru Chrome dan Firefox.
Rickard Elimää
2

Dalam jawaban str, nilai 'undefined' akan dikembalikan alih-alih nilai default yang ditetapkan jika properti tidak ditentukan. Ini terkadang dapat menyebabkan bug. Berikut ini akan memastikan defaultVal akan selalu dikembalikan jika properti atau objek tidak ditentukan.

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;
    }
}
Cao Shouguang
sumber
Versi perbaikan Hardy Le Roux dari kode saya tidak bekerja dengan let myObj = {} getSafe (() => myObj.ab, "nice"), sementara milik saya berfungsi. Ada yang menjelaskan kenapa?
Cao Shouguang
2

Lodash memiliki getmetode yang memungkinkan default sebagai parameter ketiga opsional, seperti yang ditunjukkan di bawah ini:

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>

Pureferret
sumber
2

Bayangkan kita ingin menerapkan serangkaian fungsi ke xjika dan hanya jika xbukan nol:

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

Sekarang katakanlah kita perlu melakukan hal yang sama untuk y:

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

Dan sama untuk z:

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

Seperti yang Anda lihat tanpa abstraksi yang tepat, kami akan menggandakan kode berulang kali. Abstraksi seperti itu sudah ada: monad Maybe .

The Mungkin monad memegang kedua nilai dan konteks komputasi:

  1. Monad menjaga nilai tetap aman dan menerapkan fungsi padanya.
  2. Konteks komputasi adalah pemeriksaan nol sebelum menerapkan fungsi.

Penerapan yang naif akan terlihat seperti ini:

⚠️ Penerapan ini hanya untuk tujuan ilustrasi! Ini bukanlah bagaimana seharusnya dilakukan dan salah di banyak tingkatan. Namun ini akan memberi Anda gambaran yang lebih baik tentang apa yang saya bicarakan.

Seperti yang Anda lihat, tidak ada yang bisa pecah:

  1. Kami menerapkan serangkaian fungsi ke nilai kami
  2. Jika pada titik mana pun, nilainya menjadi null (atau tidak ditentukan), kami tidak lagi menerapkan fungsi apa pun.

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>


Lampiran 1

Saya tidak dapat menjelaskan apa itu monad karena ini bukan tujuan dari posting ini dan ada orang di luar sana yang lebih baik daripada saya. Namun seperti yang dikatakan Eric Elliot dalam posting blog sebelumnya, JavaScript Monads Made Simple :

Terlepas dari tingkat keahlian atau pemahaman Anda tentang teori kategori, menggunakan monad membuat kode Anda lebih mudah digunakan. Gagal memanfaatkan monad dapat membuat kode Anda lebih sulit untuk dikerjakan (misalnya, callback hell, cabang bersyarat bersarang, lebih banyak verbositas).


Lampiran 2

Inilah cara saya menyelesaikan masalah Anda menggunakan Mungkin monad from

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/[email protected]/dist/monet.js"></script>
<script>const {Maybe} = Monet;</script>

customcommander
sumber
0

Saya menjawab ini sebelumnya dan kebetulan melakukan pemeriksaan serupa hari ini. Penyederhanaan untuk memeriksa apakah ada properti bertitik bertingkat. Anda dapat mengubah ini untuk mengembalikan nilai, atau beberapa default untuk mencapai tujuan Anda.

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;

}

Dalam pertanyaan Anda, Anda dapat melakukan sesuatu seperti:

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

Saya biasanya menggunakan seperti ini:

 var x = object.any ? object.any.a : 'def';
Vansuita Jr.
sumber
0

Anda dapat menghindari kesalahan dengan memberikan nilai default sebelum mendapatkan properti

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

Ini berfungsi baik dengan array juga:

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)

Igor Parra
sumber