ES2020 mengenalkan fitur Optional Chaining, yang sudah kita bahas kisi-kisinya pada Bekerja dengan Ketiadaan di JavaScript (Bagian 2). Kali ini, kita akan menyelami fitur baru ini lebih dalam.

Motivasi Optional Chaining

JavaScript akan memberikan TypeError jika kita mencoba mengakses properti dari undefined atau null. Masuk akal memang, tapi jika kita bekerja dengan objek dengan struktur berlapis, sifat tersebut menjadi hal yang penting untuk diperhatikan.

let obj = {};

obj.a; // undefined
obj.a.b; // TypeError: Cannot read property 'b' of undefined

Dengan operator typeof dan operator logika &&, kita bisa memastikan apakah akses properti objek bisa dilakukan dengan aman.

let obj = {};

(typeof obj.a !== 'undefined' && obj.a !== null ) && obj.a.b; // false, tapi tidak error

Lebih jauh lagi, kita bisa menggunakan operator logika || untuk mengatur nilai fallback.

let obj = {};

(typeof obj.a !== 'undefined' && obj.a !== null ) && obj.a.b || "tidak ada nilai";
// "tidak ada nilai"

Tapi, tidakkah sintaks itu terlalu panjang? Sintaks yang panjang dengan menggunakan operator yang tidak dibuat untuk kasus pakai yang dilakukan akan membuka peluang terjadinya bug, dan mencederai keterbacaan kode. Optional Chaining dihadirkan untuk memecahkan masalah tersebut.

Penggunaan Optional Chaining

Sintaks

Optional Chaining pada JavaScript memiliki sintaks tanda tanya diikuti titik (?.). Terdapat tiga tempat di mana sintaks Optional Chaining dapat digunakan:

  • Akses properti objek secara statis (a?.b)
  • Akses properti objek secara dinamis (a?.[expr])
  • Pemanggilan fungsi (a?.(...args))

Cara Kerja Optional Chaining

Operator Optional Chaining membutuhkan dua ekspresi sebagai operan, di sebelah kiri (LHS) dan sebelah kanan (RHS) dari operan tersebut (misalnya lhs?.rhs). Jika LHS bernilai nullish, maka operasi Optional Chaining akan langsung mengembalikan nilai undefined dan ekspresi RHS tidak akan dievaluasi dan tidak akan mengembalikan error.

const obj = {};

obj.a?.b(); // undefined
obj.a?.[++x]; // undefined
obj.a?.b.c().d?.[e].f; // undefined

Sifat ini disebut short-circuiting. Dengan adanya sifat ini, sepanjang apapun operan RHS, ia tidak akan dievaluasi jika LHS bernilai nullish sehingga tidak akan menimbulkan error.

Caveat

Perlu diperhatikan sekali bahwa ketika LHS bernilai bukan nullish, maka RHS akan dievaluasi, sehingga Optional Chaining perlu dilakukan pada tempat-tempat di mana kita merasa mungkin akan terjadi nilai nullish, pada bagian RHS operator Optional Chaining pertama sekalipun.

const obj = { a: 42 };

obj.a?.length.toString(); // TypeError

Pada contoh di atas, obj.a tidak bernilai nullish sehingga obj.a.length dievaluasi bernilai undefined, lalu terjadi TypeError karena undefined tidak memiliki properti toString. Agar lebih aman, kita harus mengubahnya menjadi:

obj.a?.length?.toString();

Kenapa tidak dibuat agar satu operasi Optional Chaining mencakup keseluruhan ekspresi, jadi jika ada nilai nullish di RHS pun akan mengembalikan undefined alih-alih TypeError?

Desain tersebut dibuat secara sengaja agar pengembang dapat mengindikasikan percabangan pada kodenya secara eksplisit, karena pada dasarnya operator ini bekerja sebagai percabangan if-else. Kode jadi akan lebih mudah di-debug ketika terjadi kesalahan karena eksplisitasnya.

Contoh Kasus Optional Chaining

Akses Properti Statis

Misalnya Anda bekerja dengan API untuk mengambil data pengguna seperti di bawah ini, dan Anda ingin mengambil kode pos pengguna tersebut.

{
    "name": "John Doe"
    "address": {
        "province": "DKI Jakarta",
        "city": "Jakarta Selatan",
        "zip_code": 12510
    }
}

Tetapi, jika pengguna belum mengatur alamat, maka API akan mengembalikan nilai null untuk atribut address-nya. Anda bisa menggunakan Optional Chaining untuk mengakses zip_code dengan aman.

const zipCode = response.address?.zip_code;

Akses Elemen Array dan Properti Dinamis

Masih dengan contoh yang sama, tapi kali ini pengguna memiliki atribut keranjang belanja cart berupa array, dan Anda ingin mendapatkan barang pertama pada keranjang belanja tersebut.

{
    "name": "John Doe"
    "cart": [
        {
            "id": 1,
            "name": "Magic Keyboard"
        }
    ]
}

Tetapi, jika pengguna belum menambahkan barang ke keranjang sama sekali, maka nilai cart akan menjadi null. Anda bisa melakukannya dengan cara berikut.

const firstItem = response.cart?.[0];

Atau, katakanlah Anda ingin mengambil barang acak dari keranjang tersebut untuk diberi diskon dadakan, maka operator akses properti dinamis bisa digunakan.

const item = response.cart?.[
  Math.round(Math.random() * (response.cart.length - 1))
];

Akses Fungsi atau Metode

Masih dengan API yang sama dengan contoh di atas, Anda tahu bahwa properti cart bisa bertipe array atau bernilai null. Anda ingin menjumlahkan harga seluruh barang dalam keranjang belanja pengguna. Anda bisa melakukannya dengan aman dengan bantuan Optional Chaining pada pemanggilan fungsi.

const total = response.cart?.reduce?.(
  (subtotal, item) => subtotal + item.price,
  0
);

Dengan begitu, jika cart tidak memiliki metode reduce, ekspresi ini akan mengembalikan undefined.

Satu hal yang harus diperhatikan pada kasus pakai ini, operator Optional Chaining tetap konsisten dengan cara kerjanya. Ketika nama fungsi yang dipanggil tidak bernilai nullish, meskipun ia bukanlah sebuah fungsi, maka ia akan tetap dipanggil. Operator ini tidak memeriksa apakah atribut yang dipanggil adalah fungsi atau bukan. Risiko TypeError tetap ada.

const obj = { a: 42 };

obj.a?.(); // TypeError: obj.a is not a function

Kolaborasi dengan Nullish Coalescing Operator

Tentu mengembalikan nilai undefined bukan jawaban yang ideal untuk seluruh kasus penggunaan ekspresi ini. Kita perlu suatu cara untuk mengatur nilai kembalian fallback, yang disediakan oleh Nullish Coalescing Operator (??).

Nullish Coalescing Operator akan mengevaluasi dan mengembalikan ekspresi di sebelah kanan jika operan sebelah kiri bernilai nullish.

const streetName = response.address?.street_name ?? "Belum mengatur alamat";

Sifat yang perlu diperhatikan dari operasi ini adalah ia juga short-circuiting, jika operan kiri bernilai nullish, ekspresi pada operan kanan tidak akan dievaluasi sama sekali.

const counter = 0;
const obj = { counter: 1 };

obj.counter++ ?? ++counter;

console.log(obj.counter); // 2
console.log(counter); // 0

Yang Tidak Dilakukan Optional Chaining Operator

Ada beberapa ekspresi Optional Chaining yang dipertimbangkan oleh tim TC39, tetapi diputuskan agar tidak didukung karena ketiadaan kasus penggunaan yang meyakinkan di dunia nyata, atau alasan-alasan lain. Berikut adalah beberapa contohnya.

  • Konstruksi objek: new a?.()
  • Template Literal: a?.`string`
  • Konstruktor atau Template Literal dalam/setelah Optional Chain: new a?.b(), a?.b`string`
  • Assignment: a?.b = c
  • Super: super?.(), super?.foo

Referensi dan Bacaan Lanjutan