Cara Menggunakan Async/Await di JavaScript dengan Benar
async/await membuat kode asinkron terbaca seperti kode biasa, tapi salah menempatkan await bisa membuat program lambat. Panduan ini menjelaskan urutannya.
Kamu menekan tombol simpan, lalu halaman terasa diam beberapa detik karena menunggu dua permintaan data berjalan bergantian. Padahal keduanya tidak saling bergantung. Masalah seperti ini sering muncul setelah orang mulai memakai async/await tanpa memahami urutan eksekusinya. Async/await adalah cara menulis kode asinkron di JavaScript supaya terbaca dari atas ke bawah, dan itu berguna selama kamu tahu kapan harus menunggu dan kapan tidak.
Apa yang sebenarnya dilakukan async dan await
Menempelkan kata async di depan fungsi membuat fungsi itu selalu mengembalikan Promise. Nilai apa pun yang kamu return akan dibungkus otomatis. Kalau fungsi melempar error, Promise-nya berubah menjadi rejected, bukan meledak di tempat kejadian.
Kata await hanya bisa dipakai di dalam fungsi async (atau di top-level module pada lingkungan yang mendukung). Fungsinya sederhana: menghentikan eksekusi fungsi itu sampai Promise yang ditunggu selesai, lalu mengembalikan nilainya. Yang berhenti hanya fungsi tersebut. Sisa program tetap jalan.
async function ambilProfil(id) {
const res = await fetch(`/api/users/${id}`);
const data = await res.json();
return data;
}
// Di luar, ini tetap Promise
const hasil = ambilProfil(7);
console.log(hasil instanceof Promise); // true
Poin terakhir itu sering terlewat. Banyak pemula menulis const user = ambilProfil(7) lalu bingung kenapa user.name bernilai undefined. Jawabannya: ambilProfil belum selesai, kamu hanya memegang objek Promise-nya.
Urutan eksekusi yang perlu kamu pegang
Kalau kamu membaca fungsi async dari atas ke bawah, anggap setiap await sebagai titik jeda. Kode setelah await tidak dijalankan sampai Promise-nya selesai. Karena itu, dua baris berikut memakan waktu dua kali lipat, meski kamu hanya menginginkan data yang berbeda:
const profil = await ambilProfil(id);
const pesanan = await ambilPesanan(id);
Kedua permintaan berjalan berurutan. Selama permintaan pertama menunggu jawaban dari server, permintaan kedua belum dimulai sama sekali. Kalau masing-masing butuh 400 ms, totalnya sekitar 800 ms. Pengguna merasakan jeda itu.
Membiasakan diri membaca alur async secara berurutan akan terasa lebih mudah kalau kamu sudah paham bagaimana JavaScript menangani event. Pola pikir yang sama dipakai saat menangani klik dan callback, seperti dijelaskan pada panduan memahami event JavaScript untuk pemula.
Menjalankan permintaan secara paralel dengan Promise.all
Kalau dua permintaan tidak saling bergantung, jalankan bersamaan. Mulai semuanya dulu, baru tunggu hasilnya:
const [profil, pesanan] = await Promise.all([
ambilProfil(id),
ambilPesanan(id),
]);
Sekarang totalnya mendekati waktu permintaan terlama, bukan jumlah keduanya. Ada satu perilaku yang perlu kamu sadari: Promise.all langsung gagal begitu salah satu Promise ditolak. Kalau kamu ingin tetap mendapat hasil dari permintaan yang berhasil, gunakan Promise.allSettled dan periksa status tiap hasil.
const hasil = await Promise.allSettled([
ambilProfil(id),
ambilPesanan(id),
]);
hasil.forEach((item) => {
if (item.status === 'fulfilled') {
console.log(item.value);
} else {
console.warn('Gagal:', item.reason);
}
});
Aturan praktisnya: pakai Promise.all kalau semua data wajib ada, dan Promise.allSettled kalau sebagian data masih berguna meski ada yang gagal.
Menangani error dengan try/catch
Promise yang ditolak saat di-await akan dilempar sebagai error biasa. Artinya kamu bisa memakai try/catch seperti kode sinkron:
async function simpan(data) {
try {
const res = await fetch('/api/simpan', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) {
throw new Error(`Server menolak: ${res.status}`);
}
return await res.json();
} catch (err) {
console.error('Gagal menyimpan', err);
throw err;
}
}
Perhatikan pemeriksaan res.ok. fetch tidak melempar error untuk respons 404 atau 500, jadi tanpa pemeriksaan ini kegagalan server akan lolos diam-diam. Melempar error di dalam try lalu menangkapnya di catch membuat alur gagal tetap terlihat di satu tempat.
Kesalahan yang paling sering muncul
- Lupa await. Memanggil fungsi async tanpa
awaitberarti kamu memegang Promise, bukan hasilnya. Kode berikutnya berjalan sebelum data siap. - await di dalam loop.
for...ofdenganawaitdi dalamnya memproses item satu per satu. Untuk 20 item yang tidak saling bergantung, itu pemborosan waktu. Kumpulkan dulu Promise-nya, laluawait Promise.all. - Memakai await di fungsi yang tidak async. Ini error sintaks, bukan bug logika. Baca pesan errornya baik-baik; biasanya menunjuk baris yang tepat.
- Menangkap error terlalu jauh. Satu
try/catchyang membungkus sepuluh operasi berbeda membuat kamu sulit tahu bagian mana yang gagal. Pecah per langkah. - Mengabaikan pembatalan. Permintaan yang sudah tidak relevan, misalnya setelah pengguna pindah halaman, tetap berjalan sampai selesai. Gunakan
AbortControlleruntuk menghentikannya.
Kalau kode asinkronmu mulai terasa berantakan dan sulit dilacak, teknik pelacakan di tips debugging JavaScript untuk pemula bisa membantumu menemukan titik di mana nilai belum siap.
Kapan sebaiknya tidak memakai await
Tidak semua Promise perlu ditunggu segera. Kadang kamu hanya ingin mengirim laporan ke server dan tidak peduli kapan selesai:
// Kirim tanpa menahan alur utama
kirimLog(aktivitas).catch((err) => {
console.warn('Log gagal dikirim', err);
});
Beri catch agar Promise yang gagal tidak memicu unhandled rejection. Pola ini cocok untuk pekerjaan latar yang tidak memengaruhi tampilan. Untuk pekerjaan yang hasilnya dibutuhkan pengguna, tetap tunggu.
Kalau kamu terbiasa dengan forEach dan map, ingat bahwa keduanya tidak menunggu callback async. Memakai await di dalam forEach tidak akan menahan apa pun. Perbandingan perilaku tiap metode iterasi dibahas lebih rinci di panduan memilih metode iterasi array di JavaScript.
Ringkasan
Async/await mengubah cara kamu membaca kode asinkron, tapi tidak mengubah aturan waktu di baliknya. Setiap await adalah titik tunggu, jadi tempatkan hanya di langkah yang memang butuh hasil sebelumnya. Untuk operasi yang mandiri, jalankan bersamaan lewat Promise.all. Bungkus langkah yang bisa gagal dengan try/catch, dan periksa status respons HTTP.
Pertanyaan yang Sering Diajukan
Apa bedanya async/await dengan Promise biasa?
Keduanya bekerja di atas mekanisme yang sama. Promise memakai .then() dan .catch(), sedangkan async/await menuliskan alur yang sama dengan sintaks mirip kode sinkron. Async/await tidak menggantikan Promise; ia hanya cara lain menulisnya, dan nilai balik fungsi async tetap Promise.
Bisakah await dipakai di luar fungsi async?
Di dalam module modern, await bisa dipakai di level teratas. Di file script biasa atau di dalam fungsi non-async, await akan memicu error sintaks. Solusi amannya: bungkus dalam fungsi async lalu panggil fungsi tersebut.
Kenapa kode saya terasa lambat setelah memakai await?
Kemungkinan besar kamu menjalankan permintaan yang sebenarnya mandiri secara berurutan. Periksa apakah setiap await benar-benar membutuhkan hasil dari langkah sebelumnya. Kalau tidak, ubah menjadi Promise.all agar berjalan bersamaan.