Dalam JavaScript, nilai undefined dan null yang mengindikasikan ketiadaan nilai diklasifikasikan sebagai nilai nullish karena kemiripan-kemiripannya. Meskipun begitu, banyak catatan yang mengingatkan kita untuk tidak menyamakan keduanya, misalnya pada kaitannya dengan deklarasi variabel dan parameter fungsi.

Nilai nullish juga memerlukan perhatian khusus dalam konteks bekerja dengan objek. Mari kita telaah.

Nilai Nullish dalam Objek

Jika kita mencoba mengakses variabel yang belum dideklarasikan, JavaScript akan mengeluarkan ReferenceError. Tapi apa yang terjadi jika kita mencoba mengakses properti objek yang belum dideklarasikan? JavaScript akan mengembalikan nilai undefined.

console.log(a); // ReferenceError: a is not defined

const b = {};

console.log(b.c); // undefined

Sekarang, anggap kita mempunyai objek data produk komponen komputer seperti di bawah. Kasusnya kita ingin mengambil nilai maksimum diskonnya.

const cpu = {
  name: "AMD Ryzen 3700X",
  price: {
    base: 4950000,
    discount: {
      rate: 0.1,
      maxAmount: 100000
    }
  }
};

const discountText = "IDR" + cpu.price.discount.maxAmount; // IDR100000

Sekarang, anggap data tersebut kita dapatkan dari API, dan terdapat konvensi bahwa produk yang tidak memiliki batas diskon maksimum tidak memiliki properti price.discount.maxAmount. Apa yang akan terjadi?

const cpu = {
  name: "AMD Ryzen 3700X",
  price: {
    base: 4950000,
    discount: {
      rate: 0.1
    }
  }
};

const discountText = "IDR" + cpu.price.discount.maxAmount; // IDRundefined

Lebih jauh lagi, apa yang terjadi jika terdapat konvensi produk yang tidak memiliki diskon mengembalikan null pada properti price.discount-nya?

const cpu = {
  name: "AMD Ryzen 3700X",
  price: {
    base: 4950000,
    discount: null
  }
};

const discountText = "IDR" + cpu.price.discount.maxAmount;
// Uncaught TypeError: cannot read property 'maxAmount' of null

Dari dua kasus di atas, terlihat bahwa akses properti nullish bisa menjadi masalah jika tidak dilakukan dengan hati-hati.Cara klasik untuk menanggulanginya adalah menggunakan operator logika && dan ||.

Operator Logika untuk Mengamankan Akses Properti

Dalam JavaScript, hasil operasi logika terhadap nilai-nilai non-boolean akan mengembalikan salah satu operannya. Misalnya a && b, jika a bernilai non-boolean yang bersifat truthy, operasi tersebut akan mengembalikan b. Jika a falsy, ia akan mengembalikan nilainya sendiri. Dalam hal operator ||, operasi a || b akan mengembalikan a jika a truthy, atau mengembalikan b jika ia falsy.

Baik && maupun || memiliki sifat short-circuit. Artinya, dalam kasus a && b, jika a falsy maka ia akan langsung mengembalikan b tanpa mengevaluasinya. Sementara itu, operator || akan melakukan hal yang sama jika operan sebelah kirinya bernilai truthy.

"string" && 42; // 42
undefined && 42; // undefined
null && 42; // null

"string" || 42; // "string"
undefined || 42; // 42
null || 42; // 42

undefined && false || 42; // 42

Memanfaatkan sifat operator logika tersebut, maka kita bisa melakukan ini.

const cpu = {
  name: "AMD Ryzen 3700X",
  price: {
    base: 4950000,
    discount: null
  }
};

const maxDiscount = cpu &&
  cpu.price &&
  cpu.price.discount &&
  cpu.price.discount.maxAmount ||
  "Belum ada diskon untuk produk ini.";
// "Belum ada diskon untuk produk ini."

Akses properti objek menjadi lebih aman karena properti bernilai nullish bisa ditangani dengan aman. Bahkan, kita juga menambahkan nilai default di sana.

Tetapi...

Metode tersebut akan gagal, jika nilai yang kita ekspektasikan untuk dikembalikan adalah nilai yang falsy. Katakanlah ada perubahan konvensi API lagi. Sekarang, jika tidak ada maksimum diskon, maka price.discount.maxAmount akan mengembalikan 0. Apa yang akan terjadi?

const cpu = {
  name: "AMD Ryzen 3700X",
  price: {
    base: 4950000,
    discount: {
      rate: 0.1,
      maxAmount: 0
    }
  }
};

const maxDiscount = cpu &&
  cpu.price &&
  cpu.price.discount &&
  cpu.price.discount.maxAmount ||
  "Belum ada diskon untuk produk ini.";
// "Belum ada diskon untuk produk ini."

Padahal, 0 adalah nilai yang valid untuk nilai yang kita cari dan hendak kita evaluasi. Tetapi, karena sifat operator logika di atas, nilai falsy memantik short-circuiting yang berujung pada dikembalikannya nilai default "Belum ada diskon untuk produk ini."

Sebenarnya ada cara untuk lebih aman lagi, yaitu menguji setiap properti hanya untuk nilai nullish, seperti di bawah ini.

(typeof cpu !== 'undefined' && cpu !== null) && 
  (typeof cpu.price !== 'undefined' && cpu.price !== null) && ...

Silakan bayangkan (atau coba) sendiri akan sepanjang apa ekspresi tersebut. Adakah cara yang lebih baik?

Optional Chaining dan Nullish Coalescing Operator

Berangkat dari permasalahan akses properti nullish dalam objek tadi, ES2020 memperkenalkan dua fitur baru: optional chaining dan null coalescing operator. Kedua fitur ini memudahkan kita dalam menanggulangi permasalahan nilai nullish di dalam objek, terutama dalam penelusuran properti objek yang berlapis alias nested.

Optional Chaining Operator

Operator optional chaining memungkinkan pengembang mengakses properti yang berada pada kedalaman objek tanpa harus melakukan validasi apakah referensi bernilai nullish. Sintaksnya adalah tanda tanya diikuti titik (?.). Mari kita coba dengan contoh kita tadi.

// alih-alih menulis seperti ini
(typeof cpu !== 'undefined' && cpu !== null) && cpu.price;

// kita bisa menuliskannya lebih pendek seperti ini!
cpu?.price;

Cara kerja operator ini adalah mengembalikan nilai undefined jika operan pada sisi kiri ? memiliki nilai nullish. Jika operan pada sisi kiri memiliki nilai selain nilai nullish, maka operasi akan berjalan seperti chaining biasa (misalnya a.b). Operator ini bersifat short-circuiting, yang artinya tidak mengevaluasi operan sisi kanan jika operan sisi kiri bernilai nullish.

let a = { b: "foo" };

a?.b; // foo
a?.b?.c // undefined
a?.b?.c?.d // undefined, karena c undefined
a?.b?.c?.d.e.f.g.h; // undefined, tidak error karena short-circuit di c

Namun, perlu diingat bahwa nilai nullish berbeda dengan undeclared variable. Jika kita mencoba mengakses referensi yang belum pernah dideklarasikan dengan optional chaining operator sekalipun, kita akan mendapatkan ReferenceError.

foo?.bar; // ReferenceError: foo is not defined

Nullish Coalescing Operator

Dengan optional chaining operator, kita memiliki alternatif lebih singkat dan akurat dari pengecekan validitas atribut dengan &&. Sekarang tinggal mengganti bagian || untuk mengatur nilai fallback, yang bisa kita lakukan menggunakan nullish coalescing operator.

Sintaks operator ini adalah ??. Cara kerjanya adalah mengembalikan nilai operan sebelah kanan jika operan di sebelah kiri bernilai nullish. Jika digabungkan dengan optional chaining, operator ini membuat akses atribut menjadi lebih aman dan lebih jelas dibaca.

let a = { b: "foo" };

a?.b?.c ?? "bar"; // bar

Setelah berjumpa dengan operator optional chaining dan null coalescing, mari kita bandingkan dengan metode akses atribut klasik sekarang.

(typeof a !== 'undefined' && a !== null) &&
(typeof a.b !== 'undefined' && a.b !== null) &&
(typeof a.b.c !== 'undefined' && a.b.c !== null) &&
a.b.c || "bar";

a?.b?.c ?? "bar";

Jadi lebih singkat dan jelas bukan?


Demikian penanganan nilai nullish dalam kaitannya dengan objek. Dalam mengakses atribut dalam objek menggunakan operator && dan ||, nilai null dan undefined disamakan dengan nilai-nilai falsy, sehingga dapat terjadi false negative di mana nilai falsy yang valid dianggap tidak valid. Dengan operator optional chaining dan nullish coalescing yang diperkenalkan ES2020, akses atribut objek yang berpotensi nullish menjadi lebih aman dan singkat.

Operator nullish coalescing pada dasarnya didesain sebagai sintaks komplemen dari operator optional chaining. Namun, optional chaining sendiri memiliki lebih banyak kasus pakai dari yang diulas pada tulisan ini. Untuk mengeksplorasinya, akan ada tulisan lanjutan yang lebih mendalam untuknya. Stay tuned!


Referensi dan Bacaan Lanjutan