Javascript

Memahami Async Await dan Promise dalam JavaScript Modern

22 Feb 2025 9 menit baca

"Pahami konsep asynchronous, Promise, dan Async/Await pada JavaScript modern dengan contoh kode praktis dan mudah dipahami developer pemula."

Memahami Async Await dan Promise dalam JavaScript Modern

Dalam dunia pengembangan web modern, asynchronous programming (pemrograman asinkron) merupakan salah satu pilar paling krusial yang wajib dikuasai oleh setiap developer JavaScript. Mengapa demikian? Karena JavaScript secara alami beroperasi pada model single-threaded (satu utas eksekusi), artinya ia hanya dapat mengeksekusi satu instruksi dalam satu waktu.

Bayangkan jika aplikasi web Anda harus mengambil data besar dari server database atau mengunggah berkas ribuan megabita secara sinkron (blocking). Seluruh antarmuka pengguna (UI) akan membeku (freeze), tombol tidak dapat diklik, dan animasi terhenti hingga proses selesai. Di sinilah konsep Promise dan Async/Await hadir sebagai penyelamat untuk menangani operasi yang membutuhkan waktu tunggu tanpa mengganggu kenyamanan pengguna.

Daftar Pembahasan

Mengenal Karakter Single-Threaded dan Event Loop di JavaScript

Sebelum melangkah ke kode, kita perlu memahami bagaimana mesin (engine) JavaScript seperti V8 di Google Chrome atau Node.js memproses instruksi. JavaScript memiliki komponen utama bernama Call Stack, tempat semua fungsi yang dipanggil dicatat dan dieksekusi secara berurutan dengan prinsip LIFO (Last In, First Out).

Namun, bagaimana JavaScript bisa melakukan tugas berdurasi panjang seperti setTimeout, membaca file, atau memanggil HTTP request tanpa memblokir Call Stack? Jawabannya adalah melalui bantuan Web APIs (di browser) atau C++ APIs (di Node.js) serta mekanisme Event Loop:

  • Call Stack: Menjalankan fungsi sinkron satu per satu.
  • Web APIs / Background Worker: Menangani timer, fetch jaringan, atau I/O di latar belakang.
  • Callback & Microtask Queue: Menampung fungsi hasil dari operasi asinkron yang siap dieksekusi. Microtask Queue memiliki prioritas lebih tinggi untuk menangani Promise.
  • Event Loop: Bertugas terus-menerus memeriksa: jika Call Stack kosong, ambil tugas pertama di antrean Microtask dan masukkan ke Call Stack.

Evolusi Penanganan Asinkron: Dari Callback Hell Menuju Promise

Pada era awal JavaScript, satu-satunya cara menangani operasi asinkron adalah dengan Callback Function, yaitu fungsi yang dikirimkan sebagai argumen untuk dipanggil kembali saat proses selesai. Namun, ketika beberapa operasi asinkron harus dijalankan berurutan (misal: login user → ambil profil → ambil daftar pesanan → proses pembayaran), kode akan membentuk piramida bercabang yang sangat sulit dibaca dan dirawat, yang dikenal dengan sebutan Callback Hell:

// Contoh Callback Hell (Piramida Menjorok yang Sulit Dirawat)
loginUser(email, password, function(user) {
    getUserProfile(user.id, function(profile) {
        getOrderHistory(profile.accountId, function(orders) {
            processPayment(orders[0].id, function(receipt) {
                console.log("Pembayaran berhasil:", receipt);
            }, handlePaymentError);
        }, handleOrderError);
    }, handleProfileError);
}, handleLoginError);

Untuk memecahkan masalah ini, ECMAScript 2015 (ES6) memperkenalkan objek Promise yang merevolusi cara penulisan kode asinkron menjadi lebih linier dan terstruktur.

Memahami Konsep Dasar dan Status (States) pada Promise

Sebuah Promise pada dasarnya adalah objek yang mewakili penyelesaian (atau kegagalan) dari suatu operasi asinkron di masa depan serta nilai yang dihasilkannya. Sebuah Promise selalu berada di salah satu dari tiga status (states) berikut:

  • Pending: Status awal, operasi asinkron sedang berlangsung dan belum selesai.
  • Fulfilled (Resolved): Operasi berhasil diselesaikan dan menghasilkan nilai balik (value).
  • Rejected: Operasi mengalami kegagalan atau error dan menghasilkan alasan penolakan (reason/error).

Berikut adalah cara membuat objek Promise kustom secara manual menggunakan constructor new Promise():

// Membuat fungsi yang mengembalikan objek Promise
function cekKetersediaanStok(namaBarang, jumlah) {
    return new Promise((resolve, reject) => {
        console.log(`Sedang memeriksa stok untuk ${namaBarang}...`);
        
        setTimeout(() => {
            const stokTersedia = 10; // Simulasi data dari basis data
            
            if (jumlah <= stokTersedia) {
                resolve({ status: true, barang: namaBarang, kuantiti: jumlah, pesan: "Stok mencukupi!" });
            } else {
                reject(new Error(`Stok ${namaBarang} tidak cukup. Maksimal pembelian: ${stokTersedia}`));
            }
        }, 1500);
    });
}

Mengonsumsi Promise dengan .then(), .catch(), dan .finally()

Setelah sebuah fungsi mengembalikan Promise, kita dapat menangani hasilnya menggunakan tiga method rantai (chaining):

  • .then(): Dijalankan ketika Promise berada dalam status Fulfilled.
  • .catch(): Menangkap segala bentuk kegagalan atau error yang dilemparkan (status Rejected).
  • .finally(): Selalu dijalankan di akhir, baik operasi sukses maupun gagal (sangat ideal untuk mematikan status loading spinner).
// Mengonsumsi Promise menggunakan method chaining
cekKetersediaanStok("Laptop Asus ROG", 2)
    .then((hasil) => {
        console.log("Berhasil:", hasil.pesan);
        return hasil.kuantiti * 15000000; // Mengembalikan nilai ke rantai berikutnya
    })
    .then((totalBiaya) => {
        console.log("Total tagihan:", totalBiaya.toLocaleString("id-ID", { style: "currency", currency: "IDR" }));
    })
    .catch((error) => {
        console.error("Terjadi Kesalahan:", error.message);
    })
    .finally(() => {
        console.log("Pemeriksaan stok selesai. Loading spinner dinonaktifkan.");
    });

Revolusi Sintaks: Menulis Kode Asinkron Elegan Menggunakan Async/Await

Meskipun Promise chaining menggunakan .then() sudah jauh lebih baik daripada Callback Hell, kode masih tetap memiliki kurung kurawal bersarang yang cukup panjang. Pada ECMAScript 2017 (ES8), JavaScript memperkenalkan pasangan kata kunci async dan await.

Sintaks Async/Await sebenarnya hanyalah syntactic sugar (pemanis sintaks) di atas Promise. Artinya, di balik layar ia tetap menggunakan Promise, tetapi memungkinkan developer menulis kode asinkron dengan gaya seperti kode sekuensial (sinkron) yang bersih dan intuitif.

Aturan Penggunaan Async dan Await:

  • Kata kunci async ditempatkan sebelum deklarasi fungsi. Fungsi dengan awalan async secara otomatis akan selalu mengembalikan Promise.
  • Kata kunci await hanya dapat digunakan di dalam fungsi yang berstatus async (atau di level teratas berkas modul modern/Top-Level Await).
  • await akan menghentikan eksekusi baris berikutnya di dalam fungsi tersebut untuk sementara hingga Promise selesai di-resolve, lalu langsung mengembalikan nilainya.
// Contoh fungsi async await yang bersih dan mudah dibaca
async function prosesPemesananBarang() {
    try {
        console.log("Memulai proses transaksi...");
        
        // Eksekusi menunggu hasil dari cekKetersediaanStok selesai
        const hasilStok = await cekKetersediaanStok("Monitor UltraWide 34 Inch", 1);
        console.log("Status Stok:", hasilStok.pesan);
        
        // Melanjutkan ke tahap berikutnya secara linear
        console.log("Membuat invoice untuk:", hasilStok.barang);
        console.log("Transaksi berhasil diselesaikan secara mulus.");
    } catch (error) {
        console.error("Gagal memproses transaksi:", error.message);
    } finally {
        console.log("Proses transaksi selesai.");
    }
}

prosesPemesananBarang();

Penanganan Error (Error Handling) Menggunakan Blok Try-Catch

Salah satu keuntungan terbesar dari sintaks Async/Await adalah kemampuannya memanfaatkan blok try...catch standar bahasa pemrograman. Ini membuat penanganan error sinkron (seperti JSON parsing error atau TypeError) dan error asinkron (seperti jaringan putus atau status 404/500) dapat ditangkap di satu tempat yang seragam:

async function ambilDetailPengguna(userId) {
    try {
        const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
        
        // Penting: fetch TIDAK melempar error saat HTTP status 404 atau 500
        if (!response.ok) {
            throw new Error(`HTTP Error! Status: ${response.status} - ${response.statusText}`);
        }
        
        const dataUser = await response.json();
        return dataUser;
    } catch (error) {
        // Menangkap baik Network Error maupun custom thrown Error
        console.error("Gagal mengambil data user:", error.message);
        return null;
    }
}

Menjalankan Operasi Asinkron Secara Paralel: Promise.all vs Promise.allSettled

Salah satu kesalahan paling sering yang dilakukan developer pemula adalah menggunakan await secara berurutan padahal tugas-tugas tersebut tidak saling bergantung. Hal ini menyebabkan waktu eksekusi membengkak (sequential waterfall bottleneck).

Contoh kesalahan umum:

// ❌ Kurang Efisien: Berjalan berurutan (butuh total 3 detik jika masing-masing 1 detik)
const user = await fetchUser();     // Tunggu 1 detik
const posts = await fetchPosts();   // Tunggu 1 detik lagi
const photos = await fetchPhotos(); // Tunggu 1 detik lagi

Solusi yang tepat adalah menjalankannya secara simultan (paralel) menggunakan helper method pada objek Promise:

1. Promise.all() (All or Nothing)

Menjalankan semua Promise secara paralel dan selesai ketika semua Promise sukses. Namun, jika ada satu saja Promise yang gagal (rejected), seluruh operasi langsung dianggap gagal:

// ✅ Efisien: Berjalan bersamaan (hanya butuh ~1 detik untuk ketiganya)
async function muatDashboard() {
    try {
        const [user, posts, photos] = await Promise.all([
            fetchUser(),
            fetchPosts(),
            fetchPhotos()
        ]);
        
        console.log("Semua data berhasil dimuat bersamaan:", { user, posts, photos });
    } catch (error) {
        console.error("Salah satu data gagal dimuat, batalkan semua:", error);
    }
}

2. Promise.allSettled() (Tetap Ambil Semua Hasil Apapun yang Terjadi)

Diperkenalkan pada ES2020, method ini menunggu seluruh Promise selesai, baik yang sukses (status: fulfilled) maupun yang gagal (status: rejected), tanpa membatalkan satu sama lain:

async function kirimNotifikasiKeSemuaLayanan() {
    const hasil = await Promise.allSettled([
        kirimEmailNotifikasi(),
        kirimTelegramNotifikasi(),
        kirimWhatsappNotifikasi()
    ]);

    hasil.forEach((item, index) => {
        if (item.status === 'fulfilled') {
            console.log(`Layanan #${index + 1} Terkirim:`, item.value);
        } else {
            console.warn(`Layanan #${index + 1} Gagal:`, item.reason.message);
        }
    });
}

Studi Kasus Nyata: Mengambil Data dari REST API Menggunakan Fetch API

Mari kita satukan semua konsep di atas dalam sebuah fungsi praktis yang umum ditemukan dalam proyek web nyata, yaitu fungsi pencarian data produk dari API dengan timeout dan penanganan status error lengkap:

/**
 * Fungsi utilitas untuk mengambil data API dengan batas waktu (Timeout)
 * @param {string} endpoint - URL REST API
 * @param {number} timeoutMs - Batas waktu dalam milidetik (default: 5000ms)
 */
async function fetchDenganTimeout(endpoint, timeoutMs = 5000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

    try {
        const response = await fetch(endpoint, { signal: controller.signal });
        
        if (!response.ok) {
            throw new Error(`Permintaan API gagal dengan status HTTP: ${response.status}`);
        }
        
        const data = await response.json();
        return { sukses: true, payload: data };
    } catch (err) {
        if (err.name === 'AbortError') {
            return { sukses: false, pesan: `Permintaan waktu habis (Timeout lebih dari ${timeoutMs}ms)` };
        }
        return { sukses: false, pesan: err.message };
    } finally {
        clearTimeout(timeoutId); // Selalu bersihkan timer memory
    }
}

// Pengujian Fungsi:
async function jalankanUjiCoba() {
    console.log("Mengambil data postingan...");
    const hasil = await fetchDenganTimeout("https://jsonplaceholder.typicode.com/posts/1");
    
    if (hasil.sukses) {
        console.log("Judul Postingan:", hasil.payload.title);
        console.log("Isi Postingan:", hasil.payload.body);
    } else {
        console.error("Terjadi Kesalahan:", hasil.pesan);
    }
}

jalankanUjiCoba();

Kesalahan Umum (Common Pitfalls) yang Sering Dilakukan Pemula

Agar kode JavaScript Anda semakin profesional dan bebas bug, perhatikan poin-poin penting berikut:

  • Lupa Menuliskan Kata Kunci await: Jika Anda memanggil fungsi async tanpa await, variabel Anda tidak akan berisi data aktual melainkan objek Promise { <pending> }.
  • Menggunakan await di Dalam Perulangan Array.prototype.forEach: Callback di dalam forEach tidak menunggu Promise selesai. Gunakan perulangan for...of tradisional atau Promise.all(array.map(...)) jika ingin memproses array secara asinkron.
  • Tidak Menangkap Error (Unhandled Promise Rejection): Selalu bungkus kode yang menggunakan await dengan blok try...catch agar aplikasi Anda tidak mengalami *unhandled rejection* yang bisa merusak kestabilan server di Node.js atau mematikan skrip browser.

Kesimpulan dan Rekomendasi Selanjutnya

Penguasaan terhadap Promise dan Async/Await merupakan batu loncatan terbesar dalam perjalanan karir seorang developer JavaScript modern. Dengan memahami cara kerja Call Stack, Event Loop, serta kemampuan mengelola eksekusi paralel melalui Promise.all, Anda kini dapat membangun antarmuka web interaktif yang super responsif, bebas lag, dan tahan terhadap gangguan koneksi jaringan.

Pada artikel seri JavaScript berikutnya di website ini, kita akan mengeksplorasi teknik manipulasi DOM modern tanpa bantuan framework eksternal serta bagaimana menyinkronkan data UI dengan REST API secara real-time.

Tertarik memperdalam arsitektur aplikasi berbasis JavaScript, TypeScript, atau membutuhkan solusi rekayasa web skala enterprise? Jangan ragu untuk berkonsultasi dan berkolaborasi bersama kami melalui laman kontak resmi di Aguzrybudy.com.

Bagikan Artikel

Bantu teman Anda menemukan solusi ini dengan membagikan artikel ini.

Mau website yang cepat & rapi?

Saya bantu struktur halaman, UI reusable, dan optimasi performa (CWV) supaya hasilnya kebaca.