Masalahnya: akses bersarang itu rapuh
Menjangkau isi object bersarang tidak masalah, sampai salah satu tingkatnya ternyata tidak ada:
user.address bernilai undefined, dan membaca .city dari undefined akan melempar TypeError: Cannot read properties of undefined. Data di dunia nyata, seperti respons API, JSON hasil parsing, dan query DOM, penuh dengan field yang mungkin ada atau tidak, dan menulis pengecekan defensif untuk setiap tingkat cepat sekali membuat kode berantakan:
const city = user && user.address && user.address.city;
Masih lumayan terbaca dengan dua tingkat. Menyiksa kalau sudah empat. Operator ?. adalah solusi yang lebih rapi.
?. berhenti pada null dan undefined
Optional chaining membaca sebuah property hanya jika nilai di depannya bukan null atau undefined. Kalau ternyata begitu, seluruh ekspresi bernilai undefined dan berhenti.
Baris pertama membaca name seperti biasa. Baris kedua bertemu user.address, melihat undefined, lalu berhenti: sisa rantainya tidak pernah dijalankan. Baris ketiga akan melempar error tanpa ?. karena .toUpperCase() akan dipanggil pada undefined; dengan ?., semuanya diam-diam menghasilkan undefined.
Cara memikirkannya: ?. berarti "kalau yang ada di depanku null atau undefined, menyerah dan kembalikan undefined; kalau tidak, lanjutkan."
Bisa juga untuk array dan pemanggilan fungsi
Tiga sintaks, idenya sama:
?.[...]untuk akses array atau property dinamis.?.()untuk memanggil sesuatu yang mungkin fungsi, mungkin juga bukan.?.nameuntuk akses property biasa.
Ketiganya berhenti dengan cara yang sama. user?.notAMethod?.() tidak melempar error walaupun notAMethod tidak ada: ?. kedua melihat undefined lalu berhenti.
Ini sangat praktis untuk callback opsional:
Tidak perlu lagi penjaga if (typeof onDone === "function") sebelum setiap pemanggilan.
Hanya null dan undefined yang memicu short-circuit
Inilah detail yang sering menjebak orang. ?. hanya peduli pada nilai nullish. Nilai falsy lain, yaitu 0, "", false, NaN, tetap sah untuk dirangkai (setidaknya sejauh autoboxing mengizinkannya):
data.count bernilai 0, yang falsy tapi tidak nullish, jadi ?.toFixed(2) tetap dijalankan dan mengembalikan "0.00". Bandingkan dengan &&:
Versi && mengembalikan 0 karena data.count falsy sehingga rantainya berhenti. Versi ?. mengembalikan "0.00" karena 0 tidak nullish. Kalau kamu memang ingin berhenti pada 0, && yang tepat. Kalau kamu hanya ingin berhenti saat "nilainya tidak ada", ?. yang kamu butuhkan.
Posisi ? itu penting
?. melindungi nilai yang ada sebelum dirinya, bukan sesudahnya. Jadi letakkan pada tingkat yang mungkin tidak ada:
Ketiganya berhasil di sini karena tidak ada yang benar-benar hilang. Tapi kalau config.server bisa bernilai undefined, kamu butuh config.server?.host: ?. sebelum server tidak membantu, karena masalahnya adalah membaca .host dari server yang tidak ada.
Aturan yang baik: pasang ?. di setiap tingkat yang nilai di kiri-nya memang mungkin nullish. Menaburkan ?. di setiap titik "untuk jaga-jaga" justru menyembunyikan bug dan membuat kode makin berisik.
Assignment lewat ?. tidak bisa
Optional chaining hanya untuk membaca. Kamu tidak bisa memakainya di sisi kiri assignment:
user?.address?.city = "Paris"; // SyntaxError
Masuk akal kalau dipikir: apa artinya melakukan assignment ke property milik undefined? Kalau kamu perlu mengisi nilai hanya saat induknya ada, tulis secara lengkap:
Kapan memakainya, dan kapan tidak
?. unggul saat sebuah nilai memang opsional:
- Respons API yang field-nya mungkin disertakan, mungkin tidak.
- Pencarian DOM:
document.querySelector(".banner")?.remove(). - Callback yang mungkin tidak dikirim:
options.onError?.(err). - Pemanggilan library berantai yang hasil perantaranya bisa
null.
Ini alat yang salah saat nilainya seharusnya selalu ada. Menaburkan ?. untuk membungkam error dalam kasus itu mengubah bug yang jelas dan mudah ditemukan ("TypeError on line 42") menjadi bug yang diam (variabel yang secara misterius bernilai undefined tiga fungsi jauhnya). Kalau sesuatu wajib ada, biarkan ia melempar error: stack trace sedang membantumu.
Contoh yang realistis
Mengambil nilai dari respons API yang mungkin tidak lengkap:
Setiap ?. menangani satu tingkat ketidakpastian. avatarUrl berakhir sebagai undefined dengan rapi. onClick?.() memanggil handler kalau kebetulan handler itu ada.
Kamu akan melihat ?? di baris displayName: itulah separuh lain dari pola ini. ?. memberimu undefined saat ada yang hilang; ?? memungkinkanmu mengganti dengan nilai default tanpa tersandung nilai falsy yang sah seperti 0 atau "".
Berikutnya: nullish coalescing
?. dan ?? adalah ide yang sama untuk masalah yang berbeda: keduanya menganggap null dan undefined sebagai "tidak ada" dan membiarkan nilai falsy lainnya. Selanjutnya, cara ?? memberimu nilai default yang masuk akal, dan kenapa || diam-diam keliru soal itu selama bertahun-tahun.
Pertanyaan yang sering diajukan
Apa fungsi ?. di JavaScript?
Operator ?. mengakses property, indeks array, atau method hanya jika nilai di depannya bukan null atau undefined. Kalau nilainya null atau undefined, seluruh ekspresi berhenti (short-circuit) dan menghasilkan undefined alih-alih melempar error. user?.address?.city tidak akan crash saat user atau address tidak ada.
Kapan sebaiknya memakai optional chaining di JavaScript?
Pakai ?. saat sebuah nilai memang boleh tidak ada: respons API dengan field opsional, pencarian DOM yang mungkin tidak menemukan node, callback yang bisa dikirim atau tidak. Jangan memakainya untuk menutupi bug pada nilai yang seharusnya selalu ada; dalam kasus itu, nilai yang hilang adalah informasi nyata yang ingin kamu lihat.
Apa bedanya ?. dan &&?
Hasilnya sama di kebanyakan kasus, tapi ?. hanya berhenti pada null atau undefined, sedangkan && berhenti pada nilai falsy apa pun, termasuk 0, '', dan false. obj && obj.count mengembalikan 0 saat count bernilai 0; obj?.count juga mengembalikan 0, tapi rantainya hanya berhenti dengan rapi pada nilai nullish.
Apakah optional chaining bisa dipakai dengan array dan pemanggilan fungsi?
Bisa. arr?.[0] membaca indeks array dengan aman, dan fn?.() memanggil fungsi hanya jika fungsinya ada. Keduanya mengikuti aturan yang sama: kalau nilai sebelum ?. adalah null atau undefined, ekspresinya bernilai undefined dan tidak ada lagi yang dijalankan.