Saat mengetik apa itu callback javascript, callback function javascript, atau promise all di mesin pencari, kebanyakan orang sebenarnya sedang menghadapi masalah yang sama: kode JavaScript tidak berjalan sesuai urutan yang dibayangkan. Sebuah fungsi selesai lebih dulu, padahal hasilnya baru dibutuhkan setelah proses lain kelar. Artikel ini menjawab langsung kebutuhan tersebut, mulai dari pengertian callback, alasan promise lahir, sampai cara memakai Promise.all untuk menjalankan banyak tugas asynchronous sekaligus.
JavaScript berjalan pada satu thread, tetapi banyak pekerjaan di dalamnya bersifat asynchronous: mengambil data dari API, membaca file, atau menunggu timer. Agar halaman tidak membeku, pekerjaan seperti itu tidak ditunggu secara langsung. Di sinilah callback dan promise berperan sebagai cara mengatur kapan sebuah kode boleh dijalankan setelah pekerjaan lain selesai.
Apa Itu Callback dalam JavaScript?
Callback adalah fungsi yang dikirim sebagai argumen ke fungsi lain, lalu dipanggil di dalam fungsi tersebut pada waktu yang ditentukan. Dengan kata lain, callback adalah cara menitipkan instruksi: setelah tugas ini selesai, jalankan fungsi berikut. Pola ini sudah lama menjadi fondasi asynchronous JavaScript, terutama sebelum promise dan async/await populer.
Callback sebagai Nilai Fungsi
Kunci memahami callback adalah posisinya sebagai nilai. Di JavaScript, fungsi adalah objek, sehingga bisa disimpan dalam variabel, dikembalikan dari fungsi lain, dan dikirim sebagai parameter. Ketika sebuah fungsi menerima parameter berupa fungsi, fungsi itulah yang disebut callback.
function ambilData(id, callback) {
setTimeout(() => {
callback(null, { id, nama: 'Budi' });
}, 1000);
}
ambilData(7, function (error, data) {
if (error) {
console.error(error);
return;
}
console.log(data.nama);
});
Pada contoh di atas, fungsi anonim yang memeriksa error dan mencetak data.nama adalah callback. Fungsi ambilData tidak tahu apa yang akan dilakukan callback; ia hanya memanggilnya setelah data siap.
Contoh Callback di Kode Sehari-hari
Callback paling mudah dikenali pada fungsi bawaan seperti setTimeout dan addEventListener. Keduanya menerima fungsi yang baru dijalankan ketika waktu habis atau ketika sebuah peristiwa terjadi.
setTimeout(function () {
console.log('Muncul setelah dua detik');
}, 2000);
document.querySelector('#tombol').addEventListener('click', function () {
console.log('Tombol diklik');
});
Pola yang sama dipakai saat membaca file di Node.js atau mengambil data dari API. Callback memastikan kode pengolah data hanya berjalan setelah respons tersedia, bukan sebelum permintaan dikirim. Perlu dicatat, callback pada Array.map atau Array.filter bersifat sinkron dan tidak menunggu apa pun; yang asynchronous adalah callback pada operasi I/O, timer, dan event.
Callback Hell dan Batasannya
Masalah muncul ketika banyak operasi asynchronous harus berurutan. Callback disusun di dalam callback, lalu di dalam callback lagi, sehingga kode menjorok ke kanan dan sulit dibaca. Kondisi ini dikenal sebagai callback hell.
ambilPengguna(function (pengguna) {
ambilPesanan(pengguna.id, function (pesanan) {
ambilDetail(pesanan.id, function (detail) {
console.log(detail);
});
});
});
Selain sulit dibaca, penanganan error menjadi berulang dan rawan terlewat. Setiap level perlu memeriksa error sendiri, dan satu kesalahan kecil dapat membuat alur sulit dilacak. Ada juga masalah inversion of control: fungsi pemanggil menyerahkan kendali penuh kepada fungsi lain untuk menentukan kapan dan berapa kali callback dijalankan. Untuk memperkuat dasar sebelum masuk lebih jauh, panduan belajar JavaScript untuk pemula membahas fungsi, objek, dan alur eksekusi yang menjadi prasyarat memahami pola ini.
Dari Callback ke Promise
Promise adalah objek yang mewakili hasil operasi asynchronous yang belum selesai. Ia memiliki tiga status: pending saat masih berjalan, fulfilled saat berhasil, dan rejected saat gagal. Status hanya berubah sekali, sehingga hasilnya bisa dipakai berulang tanpa menjalankan ulang operasi.
Untuk referensi resmi mengenai tipe data, fungsi, dan objek promise, dokumentasi dasar-dasar JavaScript modern di MDN bisa dijadikan rujukan lengkap.
Mengubah Callback Menjadi Promise
Promise dibuat dengan konstruktor new Promise, yang menerima fungsi berisi dua parameter: resolve dan reject. Pola callback yang tadinya bersarang bisa dibungkus menjadi satu promise yang lebih rapi.
function ambilData(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!id) {
reject(new Error('ID tidak valid'));
return;
}
resolve({ id, nama: 'Budi' });
}, 1000);
});
}
Rantai then dan Penanganan Error
Hasil promise diolah dengan .then() untuk jalur sukses dan .catch() untuk jalur gagal. Keduanya mengembalikan promise baru, sehingga bisa dirantai. Gunakan .finally() untuk pekerjaan yang harus berjalan apa pun hasilnya, misalnya mematikan indikator loading.
ambilData(7)
.then((data) => {
console.log(data.nama);
return ambilPesanan(data.id);
})
.then((pesanan) => console.log(pesanan))
.catch((error) => console.error(error.message))
.finally(() => console.log('Selesai'));
Sintaks panah dan modul modern yang dipakai di contoh ini dibahas lebih rinci pada artikel fitur ES6+ dalam JavaScript, termasuk arrow function, template literal, dan import.
Memahami Promise.all dan Variannya
Promise.all menerima iterable berisi promise, lalu mengembalikan satu promise baru. Promise itu resolve dengan array hasil sesuai urutan input ketika semuanya selesai, atau reject segera ketika salah satu promise gagal. Sifat gagal cepat ini membuat Promise.all cocok untuk tugas yang saling bergantung dan tidak boleh berjalan setengah-setengah.
Contoh Promise.all
const [pengguna, pesanan, notifikasi] = await Promise.all([
fetch('/api/pengguna').then((r) => r.json()),
fetch('/api/pesanan').then((r) => r.json()),
fetch('/api/notifikasi').then((r) => r.json()),
]);
Ketiga permintaan berjalan bersamaan, bukan berurutan. Waktu total mendekati permintaan paling lambat, bukan jumlah ketiganya. Inilah alasan Promise.all sering dipakai untuk mempercepat halaman yang perlu mengambil beberapa sumber data sekaligus.
Promise.all vs Promise.allSettled vs Promise.race
- Promise.all menunggu semua berhasil dan langsung gagal bila ada satu yang reject.
- Promise.allSettled menunggu semua selesai, lalu mengembalikan status
fulfilledataurejecteduntuk setiap promise tanpa menggagalkan keseluruhan. - Promise.race mengambil hasil promise pertama yang selesai, baik berhasil maupun gagal.
- Promise.any mengambil promise pertama yang berhasil dan baru gagal bila semuanya reject.
Kapan Memakai Promise.all
Gunakan Promise.all ketika semua hasil wajib ada sebelum langkah berikutnya, misalnya memuat profil, daftar pesanan, dan notifikasi untuk satu halaman dasbor. Jika sebagian data boleh gagal tanpa menghentikan halaman, Promise.allSettled lebih tepat. Untuk membatasi waktu tunggu, Promise.race bisa dipadukan dengan timer.
Async/Await: Cara Modern Menulis Kode Asynchronous
async dan await membuat kode berbasis promise terlihat seperti kode sinkron. Fungsi yang ditandai async selalu mengembalikan promise, sedangkan await menunda eksekusi sampai promise selesai. Error ditangani dengan try/catch, sehingga alurnya lebih mudah dibaca.
async function muatDasbor() {
try {
const [pengguna, pesanan] = await Promise.all([
fetch('/api/pengguna').then((r) => r.json()),
fetch('/api/pesanan').then((r) => r.json()),
]);
return { pengguna, pesanan };
} catch (error) {
console.error('Gagal memuat dasbor:', error.message);
}
}
Perhatikan bahwa await di dalam Promise.all tetap membuat kedua permintaan berjalan paralel. Kesalahan umum adalah menulis dua await terpisah, sehingga permintaan kedua baru dimulai setelah yang pertama selesai.
Kesalahan Umum dan Praktik Terbaik
- Lupa mengembalikan nilai di dalam
.then(), sehingga rantai berikutnya menerimaundefined. - Tidak menambahkan
.catch()atautry/catch, sehingga rejection menjadi unhandled promise rejection. - Menjalankan
awaitberurutan untuk operasi yang sebenarnya independen, padahal bisa diparalelkan denganPromise.all. - Memakai
Promise.alluntuk operasi yang boleh gagal sebagian, padahalPromise.allSettledlebih sesuai. - Mencampur callback dan promise tanpa alasan jelas, sehingga alur error menjadi tidak konsisten.
Kesimpulan
Callback adalah fondasi asynchronous JavaScript: fungsi yang dititipkan untuk dijalankan setelah tugas lain selesai. Promise menyempurnakan pola itu dengan status yang jelas, rantai then, dan penanganan error yang lebih terpusat. Promise.all lalu menjadi alat utama ketika beberapa tugas asynchronous harus berjalan bersamaan dan seluruh hasilnya dibutuhkan. Dengan memahami callback, promise, Promise.all, dan async/await secara berurutan, kode JavaScript yang tadinya bersarang dan sulit dilacak bisa diubah menjadi alur yang rapi, cepat, dan mudah dirawat.
Foto sampul oleh Shahadat Rahman di Unsplash.
