Langsung ke konten
Belajar Javascript

Cara Membuat Callback Function di Javascript

Foto TaufiqOleh Taufiq
Cara Membuat Callback Function di Javascript

Pernahkah Anda merasa bingung saat harus menangani nilai yang didapat dari proses asynchronous di dalam program yang berjalan secara synchronous? Masalah ini sangat umum terjadi ketika belajar JavaScript, terutama saat Anda pertama kali berurusan dengan setTimeout, fetching data dari API, atau membaca file. Bayangkan Anda sedang memesan kopi di kedai kopi, tapi tiba-tiba harus pergi urusan mendadak. Anda tidak bisa menunggu kopi selesai, lalu Anda meminta teman untuk menerimanya. Inilah analogi sederhana dari callback function — sebuah fungsi yang Anda “titipkan” agar dipanggil setelah tugas asynchronous selesai. Yuk, kita bedah cara membuat callback function di JavaScript dari nol — mulai dari konsep dasar, masalah callback hell, hingga solusi modern menggunakan Promise.

Apa Itu Callback Function?

Callback function adalah sebuah fungsi yang kita berikan sebagai argument ke fungsi lain, dan fungsi tersebut akan dipanggil (dijalankan) di suatu titik di masa depan — biasanya setelah sebuah operasi asynchronous selesai. Jadi, callback seperti “pesan” yang Anda tinggalkan: “Kalau sudah selesai, panggil saya ya.”

Dalam JavaScript, fungsi adalah first-class citizen — artinya fungsi bisa diperlakukan seperti variabel biasa: bisa disimpan dalam variabel, dikirim sebagai argument, dan dikembalikan dari fungsi lain. Kemampuan inilah yang membuat callback function bisa bekerja.

Misalnya seperti kode berikut:

JavaScript Snippet JavaScript
const getCake = () => {
 let cake = null;
 console.log("Sedang membuat kue, silakan tunggu....");
 setTimeout(() => {
 cake = "Kue Selesai!";
 }, 3000);
 return cake;
};

const cake = getCake();
console.log(cake);
// output:
// Sedang membuat kue, silakan tunggu....
// null

Bila kita mencoba mencetak nilai cake secara langsung, hasilnya akan selalu null. Kenapa? Karena setTimeout tidak menghentikan eksekusi JavaScript. Fungsi getCake mengembalikan nilai null sebelum proses asynchronous (timeout 3 detik) selesai. Ini masalah klasik yang dihadapi semua developer pemula JavaScript.

Bagaimana Callback Function Menyelesaikan Masalah Ini?

Kembali ke analogi kedai kopi tadi. Ketika Anda harus pergi mendadak, Anda meminta teman untuk menerima kopi dan membawanya nanti. Dalam dunia JavaScript, “teman” ini adalah callback function — sebuah fungsi yang dipanggil ketika tugas asynchronous sudah selesai dilakukan.

Begini caranya kita menambahkan callback pada fungsi asynchronous:

JavaScript Snippet JavaScript
const getCake = (callback) => {
 let cake = null;
 console.log("Sedang membuat kue, silakan tunggu....");
 setTimeout(() => {
 cake = "Kue Selesai!";
 // Panggil callback dengan hasil cake
 callback(cake);
 }, 3000);
};

// Menggunakan callback
getCake((cake) => {
 console.log(cake);
});

// output:
// Sedang membuat kue, silakan tunggu....
// ---- setelah 3 detik ----
// Kue Selesai!

Perhatikan perubahan pentingnya:

  • Fungsi getCake sekarang menerima parameter callback
  • Setelah proses asynchronous selesai (di dalam setTimeout), kita memanggil callback(cake)
  • Tidak perlu lagi return cake karena hasilnya langsung disampaikan melalui callback
  • Saat memanggil getCake, kita mengirimkan sebuah fungsi anonim yang menerima parameter cake

Kode di atas memecahkan masalah asynchronous kita. Sekarang kita mendapatkan “Kue Selesai!” setelah 3 detik, bukan null.

Cara Menggunakan Callback Function: Studi Kasus Praktis

Callback function tidak hanya digunakan untuk setTimeout. Dalam pengembangan web nyata, Anda akan sering menjumpainya di berbagai skenario. Berikut beberapa contoh penerapan yang sering digunakan:

1. Fetch Data dari API

JavaScript Snippet JavaScript
// Menggunakan XMLHttpRequest (cara lama dengan callback)
function fetchData(url, callback) {
 const xhr = new XMLHttpRequest();
 xhr.open("GET", url);
 xhr.onload = function() {
 if (xhr.status === 200) {
 const data = JSON.parse(xhr.responseText);
 callback(null, data); // null = tidak ada error
 } else {
 callback(new Error("Gagal mengambil data"), null);
 }
 };
 xhr.onerror = function() {
 callback(new Error("Terjadi kesalahan jaringan"), null);
 };
 xhr.send();
}

// Menggunakan callback
fetchData("https://api.example.com/users", (error, data) => {
 if (error) {
 console.error("Error:", error.message);
 return;
 }
 console.log("Data berhasil diambil:", data);
});

Perhatikan pola error-first callback di atas — parameter pertama selalu berisi error (atau null jika tidak ada error), dan parameter kedua berisi data. Pola ini sangat umum digunakan di Node.js dan merupakan konvensi yang baik untuk diikuti.

2. Membaca File di Node.js

JavaScript Snippet JavaScript
const fs = require('fs');

// Callback untuk membaca file
fs.readFile('data.json', 'utf8', (err, data) => {
 if (err) {
 console.error("Gagal membaca file:", err.message);
 return;
 }
 
 try {
 const jsonData = JSON.parse(data);
 console.log("Isi file:", jsonData);
 } catch (parseError) {
 console.error("Gagal parse JSON:", parseError.message);
 }
});

Di sini, fs.readFile menerima callback yang akan dipanggil setelah file selesai dibaca. Callback juga menerima dua parameter: err dan data.

3. Event Listener di Browser

JavaScript Snippet JavaScript
// Event listener juga menggunakan pola callback
const button = document.querySelector('#myButton');

button.addEventListener('click', (event) => {
 // Callback function yang dijalankan saat tombol diklik
 console.log("Tombol diklik!");
 console.log("Posisi klik:", event.clientX, event.clientY);
});

Setiap kali Anda menggunakan addEventListener, sebenarnya Anda sedang mendaftarkan callback function yang akan dipanggil oleh browser ketika event tertentu terjadi.

Callback Hell: Masalah yang Harus Dihadapi

Sekarang kita sudah paham cara kerja callback. Tapi bagaimana kalau ada banyak proses asynchronous yang saling bergantung satu sama lain? Mari kita ambil contoh membuat kue lagi — langkah-langkahnya adalah:

  1. Menyiapkan bahan
  2. Membuat adonan
  3. Menuangkan adonan ke cetakan
  4. Memanggang adonan

Kalau semua langkah berjalan secara synchronous, kodenya akan terlihat rapi seperti ini:

JavaScript Snippet JavaScript
function makeACake(...rawIngredients) {
 const ingredients = gatheringIngredients(rawIngredients),
 dough = makeTheDough(ingredients),
 pouredDough = pourDough(dough),
 cake = bakeACake(pouredDough);
 console.log(cake);
}

Tapi ketika semua langkah berjalan asynchronous (misalnya masing-masing butuh waktu beberapa detik), kita terpaksa bersarang callback di dalam callback. Dan inilah yang disebut callback hell:

JavaScript Snippet JavaScript
function makeACake(...rawIngredients) {
 gatheringIngredients(rawIngredients, function(ingredients) {
 makeTheDough(ingredients, function(dough) {
 pourDough(dough, function(pouredDough) {
 bakeACake(pouredDough, function(cake) {
 console.log(cake);
 });
 });
 });
 });
}

Melihat kode seperti ini saja sudah bikin pusing, kan? Bayangkan kalau ada 10 atau 15 langkah asynchronous — kode akan menjadi “piramida” yang sulit dibaca, di-debug, dan dipelihara. Inilah yang disebut callback hell.

Gejala callback hell bisa dikenali dari beberapa ciri:

  • Banyak kurung tutup }); bertumpuk di bagian bawah kode
  • Kode semakin dalam (indentation) untuk setiap langkah baru
  • Sulit menentukan mana error handler yang tepat untuk setiap callback
  • Membaca alur logika program menjadi sangat sulit

Solusi Callback Hell: Promises

Untungnya, JavaScript menyediakan solusi yang jauh lebih rapi: Promise. Promise memungkinkan kita menuliskan kode asynchronous yang terlihat seperti kode synchronous, tanpa harus bersarang callback.

JavaScript Snippet JavaScript
function makeACake(...rawIngredients) {
 gatheringIngredients(rawIngredients).then(makeTheDough).then(pourDough).then(bakeACake).then(console.log);
}

Lihat betapa bersihnya kode di atas? Tidak ada lagi piramida callback. Setiap .then() menerima hasil dari langkah sebelumnya dan meneruskannya ke langkah berikutnya. Bahkan non-developer pun bisa mengerti alur dari kode tersebut.

Berikut contoh Promise yang lebih lengkap dan realistis:

JavaScript Snippet JavaScript
// Membuat Promise
function fetchData(url) {
 return new Promise((resolve, reject) => {
 const xhr = new XMLHttpRequest();
 xhr.open("GET", url);
 xhr.onload = function() {
 if (xhr.status === 200) {
 resolve(JSON.parse(xhr.responseText));
 } else {
 reject(new Error("Status: " + xhr.status));
 }
 };
 xhr.onerror = function() {
 reject(new Error("Network error"));
 };
 xhr.send();
 });
}

// Menggunakan Promise dengan.then() dan.catch()
fetchData("https://api.example.com/users").then(data => {
 console.log("Data:", data);
 return data[0]; // Ambil user pertama
 }).then(user => {
 console.log("User pertama:", user.name);
 }).catch(error => {
 console.error("Terjadi kesalahan:", error.message);
 }).finally(() => {
 console.log("Proses selesai");
 });

Solusi Lebih Lanjut: Async/Await

Selain Promise, ada lagi fitur yang membuat kode asynchronous terlihat semakin mirip synchronous: async/await. Fitur ini diperkenalkan di ES2017 dan kini sudah menjadi standar dalam penulisan kode asynchronous modern.

JavaScript Snippet JavaScript
async function makeACake(...rawIngredients) {
 try {
 const ingredients = await gatheringIngredients(rawIngredients);
 const dough = await makeTheDough(ingredients);
 const pouredDough = await pourDough(dough);
 const cake = await bakeACake(pouredDough);
 console.log(cake);
 } catch (error) {
 console.error("Gagal membuat kue:", error.message);
 }
}

makeACake("tepung", "telur", "gula", "mentega");

Dengan async/await, kita mendapatkan kejelasan alur program tanpa harus menggunakan rantai .then(). Perhatikan juga penggunaan try/catch — ini memudahkan penanganan error di setiap langkah asynchronous seolah-olah kita sedang menulis kode synchronous.

Perbandingan: Callback vs Promise vs Async/Await

Agar lebih jelas, berikut perbandingan ketiga pendekatan tersebut dalam satu contoh kasus yang sama:

JavaScript Snippet JavaScript
// === CALLBACK VERSION ===
function getUserCallback(id, callback) {
 setTimeout(() => {
 callback(null, { id: id, name: "Budi" });
 }, 1000);
}

getUserCallback(1, (err, user) => {
 if (err) return console.error(err);
 console.log("User:", user.name);
});

// === PROMISE VERSION ===
function getUserPromise(id) {
 return new Promise((resolve) => {
 setTimeout(() => {
 resolve({ id: id, name: "Budi" });
 }, 1000);
 });
}

getUserPromise(1).then(user => console.log("User:", user.name)).catch(err => console.error(err));

// === ASYNC/AWAIT VERSION ===
async function main() {
 try {
 const user = await getUserPromise(1);
 console.log("User:", user.name);
 } catch (err) {
 console.error(err);
 }
}

main();

Ketiganya menghasilkan output yang sama, tapi tingkat keterbacaan dan kemudahan maintenance-nya sangat berbeda.

Best Practices dan Tips Penting

Beberapa tips yang perlu diingat saat menggunakan callback function:

  • Hindari callback hell — jika Anda menemukan callback bersarang lebih dari 2-3 level, pertimbangkan untuk beralih ke Promise atau async/await
  • Gunakan error-first pattern — selalu letakkan parameter error di posisi pertama, seperti yang dilakukan oleh Node.js
  • Hindari mengembalikan callback sebagai return value — jangan mengembalikan callback dari fungsi, cukup panggil callback di dalam fungsi
  • Beri nama yang jelas — gunakan nama fungsi yang deskriptif, bukan hanya callback1, callback2
  • Gunakan arrow function — untuk callback singkat, arrow function membuat kode lebih ringkas dan mudah dibaca
  • Hindari synchronous callback bercampur — beberapa fungsi seperti Array.map() atau Array.forEach() juga menggunakan callback, tapi berjalan synchronous. Jangan bingung dengan callback asynchronous

Common Mistakes yang Sering Terjadi

Berikut beberapa kesalahan umum yang sering dilakukan developer saat bekerja dengan callback:

JavaScript Snippet JavaScript
// ❌ MELUPAKAN MEMANGGIL CALLBACK
function prosesData(data, callback) {
 if (!data) {
 // Lupa memanggil callback dengan error!
 // Program akan hang
 return;
 }
 callback(null, result);
}

// ✅ YANG BENAR
function prosesData(data, callback) {
 if (!data) {
 return callback(new Error("Data tidak valid"), null);
 }
 callback(null, result);
}

// ❌ MEMANGGIL CALLBACK BEBERAPA KALI
function fetchData(url, callback) {
 callback(null, data1); // Dipanggil di sini...
 callback(null, data2); //...dan di sini lagi! Bug!
}

// ✅ YANG BENAR - panggil callback hanya sekali
function fetchData(url, callback) {
 callback(null, data);
}

// ❌ CALLBACK HELL YANG HARUSNYA DIIKHLASI
getData(function(a) {
 getMoreData(a, function(b) {
 getEvenMoreData(b, function(c) {
 getYetMoreData(c, function(d) {
 console.log(d); // Terlalu dalam!
 });
 });
 });
});

Kesalahan paling berbahaya adalah lupa memanggil callback — program tidak akan pernah selesai dan berpotensi menyebabkan memory leak atau application hang.

Rangkuman dan Takeaway

Berikut poin-poin penting yang perlu Anda ingat:

  1. Callback function adalah fungsi yang dikirim sebagai argument ke fungsi lain, dan dipanggil setelah operasi asynchronous selesai
  2. Error-first callback adalah pola standar di mana parameter pertama berisi error (atau null)
  3. Callback hell terjadi saat callback bersarang terlalu dalam, membuat kode sulit dibaca dan dipelihara
  4. Promise dan async/await adalah solusi modern untuk menghindari callback hell
  5. Selalu pastikan callback dipanggil tepat sekali, baik saat berhasil maupun saat gagal

Memahami callback function adalah fondasi yang sangat penting sebelum melangkah ke konsep asynchronous yang lebih lanjut seperti Promise dan async/await. Kedua fitur tersebut sebenarnya dibangun di atas prinsip callback — jadi memahami callback dengan benar akan membuat Anda lebih mudah memahami konsep-konsep lanjutan tersebut. Untuk memperdalam, baca juga panduan cara kerja JavaScript asynchronous di infanthree. Jika ingin mempelajari lebih lanjut tentang Promise dan async/await, Anda bisa membaca dokumentasi resminya di MDN Web Docs: Using Promises dan MDN: asynchronous JavaScript.

Hal yang sering ditanyakan seputar Callback Function JavaScript

Apa itu callback function dalam JavaScript?

Callback adalah fungsi yang diteruskan sebagai argumen ke fungsi lain dan dieksekusi setelah operasi tertentu selesai, umum digunakan untuk operasi asinkron.

Apa masalah dari callback hell dan bagaimana solusinya?

Callback bersarang berlebihan membuat kode sulit dibaca; solusinya dengan Promise, async/await, atau memecah fungsi menjadi modul kecil.

Apa perbedaan callback sinkron dan asinkron?

Callback sinkron dieksekusi langsung dalam alur utama, sedangkan callback asinkron dijalankan setelah operasi seperti permintaan jaringan selesai.

Komentar

Memuat komentar…

Tinggalkan Komentar

Alamat email Anda tidak akan dipublikasikan.