Bayangkan Anda sedang memesan makanan lewat aplikasi. Anda menekan tombol “Pesan”, lalu kembali mengobrol atau menjelajah media sosial sambil menunggu kurir datang. Anda tidak duduk terpaku menatap layar, menunggu detik demi detik berlalu sampai makanan tiba. Konsep inilah yang, dalam dunia pemrograman JavaScript, dikenal sebagai proses asinkron.
Di balik kenyamanan antarmuka web dan aplikasi modern-mulai dari memuat data tanpa me-refresh halaman, hingga menampilkan notifikasi real-time-terdapat mekanisme yang mengatur “tunggu sebentar” tanpa benar-benar menghentikan seluruh program. Dua aktor utama dalam cerita ini adalah Promise dan sintaks async/await. Keduanya hadir untuk membantu developer mengelola operasi asinkron seperti permintaan ke server, membaca file, atau menjalankan tugas yang memakan waktu, tanpa membuat kode menjadi berantakan atau sulit diikuti.
Artikel ini akan mengajak Anda mengenal apa itu asinkron dalam JavaScript, memahami bagaimana Promise bekerja, dan melihat bagaimana async/await menyederhanakan cara kita menulis kode yang menunggu sesuatu terjadi. Dengan contoh-contoh yang mudah dicerna, kita akan mengurai bagaimana kedua konsep ini saling melengkapi dalam menyusun alur logika yang rapi, meski dijalankan secara “tidak serentak” di balik layar.
Membedah Dasar Promise Cara Kerja, State dan Pola Penanganan Error
Di balik layar, Promise itu sebenarnya cuma objek yang memegang “janji” hasil proses asinkron. Janji ini punya tiga kemungkinan state: pending (masih proses), fulfilled (berhasil), dan rejected (gagal). Begitu Promise dibuat, JavaScript langsung mengeksekusi fungsi di dalamnya, tapi hasilnya baru bisa diambil nanti lewat .then() atau await. Cara kerjanya mirip pesanan makanan di restoran: kamu pesan (membuat Promise), lalu melakukan hal lain dulu, dan pelayan akan memberi tahu ketika pesanan siap (fulfilled) atau ada masalah di dapur (rejected). Untuk gambaran singkat, lihat ringkasan state Promise berikut:
| State | Artinya | Aksi Umum |
|---|---|---|
| pending | Proses masih berjalan | Tunggu hasilnya |
| fulfilled | Proses sukses | Ambil data di then |
| rejected | Proses gagal/error | Tangani di catch |
.then()untuk menangani hasil sukses..catch()untuk menangani error atau penolakan..finally()untuk kode yang selalu jalan, apapun hasilnya.- Chaining (
promise.then(...).then(...)) untuk alur proses berurutan.
“`js
const getUser = (id) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!id) {
reject(new Error(‘ID user tidak boleh kosong’));
} else {
resolve({ id, name: ‘Raka’ });
}
}, 1000);
});
};
getUser(10)
.then((user) => {
console.log(‘User:’, user);
return user.name.toUpperCase();
})
.then((upperName) => {
console.log(‘Nama besar:’, upperName);
})
.catch((err) => {
console.error(‘Terjadi error:’, err.message);
})
.finally(() => {
console.log(‘Selesai proses getUser’);
});
“`
Mengoptimalkan Async Await Menyusun Alur Kode Asinkron yang Terbaca dan Terukur
Begitu mulai nyaman memakai async/await, godaan paling besar adalah menulis semuanya jadi satu fungsi raksasa. Padahal, alur asinkron jauh lebih mudah diikuti kalau kita memecahnya jadi blok-blok logis yang jelas. Coba biasakan diri menyusun langkah seperti cerita: ambil data, olah, lalu tampilkan. Setiap langkah punya tanggung jawab yang spesifik. Di sinilah kombinasi penamaan fungsi yang jelas, penanganan error yang rapi, dan urutan await yang terstruktur bikin kode terasa “tenang” ketika dibaca ulang nanti. Hindari await berlapis-lapis di dalam callback yang saling memanggil, karena itu mirip tumpukan tab browser yang lupa ditutup-lama-lama membingungkan.
- Gunakan fungsi kecil dengan tujuan tunggal
- Kelompokkan beberapa promise dengan Promise.all jika bisa paralel
- Bedakan antara error yang boleh “dibiarkan” dan yang wajib dihentikan
- Jaga konsistensi: selalu try/catch atau selalu lempar error ke level di atas
| Strategi | Kapan Dipakai | Dampak |
|---|---|---|
| Await berurutan | Langkah saling bergantung | Lebih mudah dibaca |
| Promise.all | Beberapa tugas bisa paralel | Waktu eksekusi lebih singkat |
| Fungsi helper | Logika mulai berulang | Kode lebih ringkas |
async function muatHalamanPengguna(userId) {
try {
const user = await ambilDetailUser(userId);
const [post, notifikasi] = await Promise.all([
ambilPostinganTerbaru(userId),
ambilNotifikasi(userId)
]);
return susunViewDashboard({ user, post, notifikasi });
} catch (err) {
logError(err);
tampilkanPesanError("Gagal memuat dashboard, coba lagi sebentar.");
throw err; // biar tetap terukur di level yang lebih tinggi
}
}
async function ambilDetailUser(id) {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error("Tidak bisa memuat data user");
return res.json();
}
Memilih Antara Promise dan Async Await Rekomendasi Praktis untuk Berbagai Skenario
Kalau bingung kapan sebaiknya pakai Promise biasa dan kapan pakai async/await, anggap saja kamu sedang pilih alat yang paling nyaman dipakai. Untuk alur logika yang linear, step-by-step, dan butuh kode yang mudah dibaca ulang, async/await hampir selalu jadi pilihan yang enak. Tapi Promise masih terasa pas ketika kamu: ingin melakukan beberapa request secara paralel, butuh chaining yang fleksibel, atau sedang ngulik utility function yang cuma mengembalikan objek Promise tanpa perlu langsung ditunggu. Sebagai gambaran cepat:
| Situasi | Disarankan | Alasan Singkat |
|---|---|---|
| Alur async mirip kode sinkron | async/await | Lebih rapi & mudah dibaca |
| Banyak operasi paralel | Promise + Promise.all |
Kontrol paralelisme lebih jelas |
| Utility yang dipakai berbagai tempat | Promise | Fleksibel dikonsumsi siapa saja |
| Kode lama berbasis then/catch | Gabungan | Bungkus dengan async/await pelan-pelan |
Di dunia nyata, kebanyakan developer akhirnya memilih mix and match. Misalnya, di level paling luar kamu menulis alur logika dengan async/await supaya enak dibaca, sementara di dalamnya tetap memanfaatkan method Promise seperti .then(), Promise.all(), atau Promise.race() untuk skenario tertentu. Pendekatan seperti ini bikin kode terasa lebih luwes dan tidak kaku. Sebagai panduan singkat, kamu bisa pegang hal-hal berikut:
- Pakai async/await untuk flow bisnis utama agar alurnya mudah diikuti.
- Pakai Promise murni saat butuh komposisi lanjutan atau utilitas generik.
- Jangan takut menggabungkan keduanya, selama error handling tetap jelas dan konsisten.
- Kalau timmu campuran senior-junior, prioritaskan keterbacaan (biasanya async/await menang di sini).
Mencegah Callback Hell Baru Strategi Mengelola Konkurensi dengan Promise all dan Promise race
Daripada terjebak dalam rantai callback yang makin lama makin menjorok ke kanan, kita bisa memanfaatkan Promise.all dan Promise.race untuk mengelola beberapa proses asinkron sekaligus dengan lebih rapi. Idenya simpel: jalankan banyak tugas paralel, lalu tangani hasilnya di satu titik yang jelas. Misalnya, ketika kita ingin memuat data user, notifikasi, dan pengaturan sekaligus, kita bisa menunggu semuanya selesai baru merender UI. Dengan begitu, alur logika tetap lurus ke bawah dan lebih gampang diikuti mata. Sebagai bonus, kalau salah satu gagal, kita bisa langsung tahu dan menampilkan pesan error yang lebih ramah, tanpa harus membongkar nested callback berlapis-lapis.
Promise.all: cocok ketika semua request penting dan hasilnya saling melengkapi.Promise.race: berguna kalau yang kita cari adalah siapa yang paling cepat: sukses atau gagal duluan.- Konkurensi yang terkontrol: tetap paralel, tapi struktur kodenya masih mudah dibaca.
| Metode | Kapan Dipakai | Perilaku Error |
|---|---|---|
Promise.all |
Butuh semua hasil sekaligus | Gagal jika satu saja gagal |
Promise.race |
Butuh respon tercepat | Ikut hasil promise paling awal |
Dengan kombinasi ini, kita bisa menulis kode yang tetap enak dibaca sambil memaksimalkan performa. Contohnya ketika ingin menerapkan timeout manual: jalankan request API dan sebuah promise timeout secara bersamaan, lalu biarkan Promise.race menentukan mana yang “menang”. Atau saat ingin menunggu banyak proses-seperti upload beberapa file-kita serahkan semua ke Promise.all, dan tinggal cek hasil akhirnya. Alurnya jelas, error-nya terkontrol, dan yang paling penting, kita terhindar dari “pola piramid” yang bikin debugging jadi mimpi buruk.
Future Outlook
Menutup perjalanan singkat kita tentang async/await dan Promise, kini kamu sudah melihat bahwa proses asinkron di JavaScript bukan lagi sekadar “callback berlapis” yang sulit diikuti. Promise memberi kita cara yang lebih terstruktur untuk menangani operasi yang berjalan di belakang layar, sementara async/await menghadirkan sintaks yang terasa lebih alami dan mudah dibaca-tanpa mengorbankan kekuatan asinkron itu sendiri.
Pada akhirnya, pilihan di tanganmu: kapan tetap menggunakan Promise secara eksplisit, kapan memanfaatkan kenyamanan async/await, dan bagaimana mengombinasikan keduanya agar alur kode tetap jernih. Semakin sering kamu bereksperimen-mencoba chaining Promise, menangani error dengan try/catch, atau membandingkan beberapa pendekatan-semakin intuitif pola asinkron ini akan terasa.
Di dunia aplikasi modern yang serba real-time dan penuh permintaan jaringan, kemampuan memahami dan mengendalikan alur asinkron bukan lagi bonus, melainkan fondasi. Setelah ini, kamu bisa mulai membongkar kode-kode lama, merapikannya dengan Promise dan async/await, dan melihat sendiri bagaimana logika programmu menjadi lebih rapi, terukur, dan siap tumbuh lebih jauh.

