Callback Adalah Fungsi yang Kamu Serahkan ke Fungsi Lain
Fungsi di JavaScript adalah nilai. Kamu bisa menyimpannya di variabel, memasukkannya ke array, dan yang terpenting di sini, memberikannya sebagai argumen. Saat kamu memberikan sebuah fungsi ke fungsi lain agar bisa dipanggil nanti, fungsi yang kamu berikan itu adalah callback.
greet tidak tahu dan tidak peduli apa yang dilakukan formatter. Ia hanya memanggilnya dengan sebuah nama dan memakai hasilnya. Kamu memilih perilakunya dengan memberikan callback yang berbeda. Fleksibilitas itulah alasan utama callback ada.
Callback Synchronous Langsung Berjalan
Tidak semua callback bersifat asynchronous. Banyak method array yang sudah kamu pakai berbasis callback, dan method itu menjalankan callback secara synchronous, sebelum panggilan luarnya selesai:
map, filter, dan reduce semuanya menerima callback dan memanggilnya sekali untuk setiap elemen, saat itu juga. Ketika map selesai, semua panggilan ke callback sudah terjadi. Tidak ada yang diantrekan untuk nanti.
Ini pola higher-order function biasa: "ini pekerjaannya, ini caranya, berikan aku hasilnya." Tidak ada event loop yang terlibat.
Callback Asynchronous Berjalan Nanti
Callback yang biasanya dimaksud orang saat menyebut "callback" adalah jenis async. Kamu menyerahkan fungsi ke API yang butuh waktu, seperti timer, request jaringan, atau pembacaan file, lalu API itu memanggil balik fungsimu saat pekerjaannya selesai.
Urutan output-nya: before, after, lalu timer fired sedetik kemudian. setTimeout tidak menjeda programmu. Ia menyerahkan callback ke runtime, langsung selesai, dan sisa script terus berjalan. Sedetik kemudian event loop mengambil callback itu dan menjalankannya.
Bentuk "selesai sekarang, panggil balik nanti" itulah model mental untuk setiap API callback async di JavaScript, dari addEventListener sampai API file Node.js yang lebih lama.
Konvensi Error-First (Node.js)
Sebelum promise ada, Node.js membakukan bentuk callback tertentu: argumen pertama adalah error (atau null), sisanya adalah hasil sebenarnya. Kamu masih akan menemuinya di kode lama dan beberapa library.
Pemanggil mengecek err lebih dulu dan langsung berhenti kalau nilainya truthy. Baru setelah itu ia memercayai hasilnya. Ini sebuah konvensi, bukan aturan yang dipaksakan bahasanya, tetapi begitu kamu melihat signature (err, result) => ..., kamu akan mengenalinya di mana-mana.
Callback Hell
Masalah dimulai saat satu langkah async bergantung pada hasil langkah lain. Setiap callback harus bersarang di dalam callback sebelumnya, dan kodenya jadi berundak-undak ke kanan:
Inilah "piramida malapetaka" yang terkenal, atau callback hell. Beberapa hal membuatnya menyakitkan:
- Alur kontrolnya zig-zag, bukan terbaca dari atas ke bawah.
- Setiap tingkat mengulang boilerplate
if (err) return ...yang sama. - Exception yang dilempar satu callback tidak merambat ke callback luar, jadi kamu harus menangani error di setiap lapisan.
- Melakukan refactor berarti mengatur ulang indentasi seluruh blok.
Kamu bisa meratakannya sedikit dengan memisahkan fungsi-fungsi bernama, tetapi masalah intinya tidak hilang: menyusun langkah async dengan callback mentah itu canggung. Itulah masalah yang dirancang untuk diselesaikan promise.
Dua Jebakan yang Perlu Diketahui
Jangan memanggil callback tanpa sengaja. Saat memberikan callback, kamu memberikan fungsinya sendiri, bukan hasil pemanggilannya.
Hati-hati dengan this. Kalau callback kamu adalah fungsi biasa yang memakai this, nilai this bergantung pada cara callback itu dipanggil, bukan tempat ia didefinisikan. Arrow function menghindari masalah ini dengan mewarisi this dari scope di sekitarnya:
Karena alasan inilah arrow function menjadi pilihan default untuk callback yang ditulis langsung di tempat.
Callback vs Promise
Callback masih muncul di API synchronous (map, forEach, sort), event listener (element.addEventListener("click", ...)), dan hook runtime tingkat rendah. Untuk pekerjaan async yang menghasilkan satu hasil, ekosistemnya hampir sepenuhnya sudah beralih ke promise.
Perbandingan singkatnya:
- Callback: langsung dan minimalis, tetapi sulit disusun. Penanganan error dilakukan manual di setiap langkah.
- Promise: sebuah nilai yang mewakili hasil di masa depan. Rangkai dengan
.then(), tangani error sekali saja dengan.catch(), dan piramidanya pun rata.
Kamu tetap perlu memahami callback: promise dibangun di atasnya, dan callback ada di mana-mana dalam kode berbasis event. Tetapi sekarang kamu jarang menulis API async baru dengan callback mentah.
Berikutnya: Promise
Promise mengambil gagasan "lakukan ini saat itu sudah siap" dan membungkusnya dalam sebuah objek yang bisa kamu oper ke mana-mana, rangkai, dan susun. Itu topik halaman berikutnya, sekaligus jembatan menuju async/await, cara kebanyakan JavaScript modern menangani pekerjaan async.
Pertanyaan yang sering diajukan
Apa itu fungsi callback di JavaScript?
Callback adalah fungsi yang kamu berikan sebagai argumen ke fungsi lain, supaya fungsi itu bisa memanggilnya nanti. setTimeout(() => console.log('hi'), 1000) memberikan sebuah arrow function sebagai callback: setTimeout menyimpannya dan memanggilnya saat timer berbunyi. Callback adalah cara asli JavaScript menangani 'lakukan ini saat itu sudah siap'.
Apa bedanya callback synchronous dan asynchronous?
Callback synchronous langsung berjalan selama panggilan yang menerimanya: [1, 2, 3].map(x => x * 2) memanggil callback tiga kali sebelum map selesai. Callback asynchronous disimpan dan dipanggil nanti, setelah suatu peristiwa terjadi: setTimeout, fs.readFile, dan event listener DOM semuanya bekerja dengan cara ini. Callback async tidak memblokir sisa kodemu.
Apa itu callback hell dan bagaimana cara menghindarinya?
Callback hell adalah bentuk piramida yang muncul saat callback async saling bergantung dan akhirnya bersarang beberapa tingkat. Alur kontrol dan penanganan error jadi sulit diikuti. Solusinya memakai promise dengan rangkaian .then(), atau lebih baik lagi, async/await. Keduanya meratakan piramida itu kembali menjadi sesuatu yang mudah dibaca.