Promise: Janji Async yang Rapi
Menangani operasi async dengan Promise: state pending/fulfilled/rejected, then/catch, dan chaining.
Solusi callback hell
Promise adalah object yang merepresentasikan hasil operasi async di masa depan. Analoginya seperti struk pengambilan laundry: kamu tidak menunggu di tempat, struknya adalah "janji" bahwa cucianmu akan siap nanti.
Tiga state
Promise selalu dalam satu dari tiga kondisi:
- pending: masih diproses
- fulfilled: berhasil, membawa hasil
- rejected: gagal, membawa error
let janji = new Promise((resolve, reject) => {
setTimeout(() => {
let berhasil = true;
if (berhasil) resolve("Data siap!");
else reject("Gagal mengambil data");
}, 1000);
});resolve mengubah state jadi fulfilled, reject jadi rejected.
Konsumsi dengan then/catch
janji
.then((hasil) => {
console.log("Berhasil: " + hasil);
})
.catch((error) => {
console.log("Error: " + error);
})
.finally(() => {
console.log("Selesai, apapun hasilnya");
});then untuk sukses, catch untuk gagal, finally selalu jalan (cocok untuk mematikan loading spinner).
Chaining: rantai yang rapi
then bisa dirangkai. Nilai yang di-return dari satu then menjadi input then berikutnya:
ambilUser()
.then(user => ambilPostingan(user.id))
.then(postingan => tampilkan(postingan))
.catch(error => console.log("Ada yang gagal: " + error));Satu catch di akhir menangkap error dari rantai mana pun. Bandingkan dengan callback hell: kode tetap datar dan mudah dibaca.
Promise bawaan yang berguna
// jalan paralel, tunggu semua selesai
Promise.all([ambilA(), ambilB()]).then(([a, b]) => {
console.log("Keduanya selesai");
});
// ambil yang paling cepat selesai
Promise.race([ambilA(), ambilB()]).then(pemenang => {
console.log("Pemenang: " + pemenang);
});Promise.all gagal total kalau satu saja gagal. Untuk kasus yang butuh hasil sebagian, ada Promise.allSettled.
Catatan teknis: then selalu mengembalikan Promise baru. Lupa me-return di dalam then memutus rantai data: then berikutnya menerima undefined. Juga, error di dalam then (bukan hanya reject) akan ditangkap catch, jadi bugs di handler tidak hilang diam-diam.
Studi kasus: muat berurutan vs paralel
Bandingkan dua pendekatan memuat tiga resource:
// sekuensial: total = jumlah semua waktu
async function sekuensial() {
let a = await ambil("/api/a");
let b = await ambil("/api/b");
let c = await ambil("/api/c");
return [a, b, c];
}
// paralel: total = waktu terlama
async function paralel() {
let [a, b, c] = await Promise.all([
ambil("/api/a"), ambil("/api/b"), ambil("/api/c")
]);
return [a, b, c];
}Kalau tiap request 1 detik, versi sekuensial butuh 3 detik, versi paralel cuma 1 detik. Aturannya: dependen (butuh hasil sebelumnya) pakai sekuensial, independen pakai Promise.all. Salah pilih bisa bikin aplikasi terasa lambat tanpa alasan.
Kesimpulan
Promise mengubah async bertingkat jadi rantai datar yang mudah dibaca. Kuasai then/catch/finally dan chaining, karena fetch API dan banyak library modern mengembalikan Promise.