Belajar dengan membaca secara berurutan

async / await — Menunggu Hasil Promise Baris demi Baris

Tunggu hasil Promise dengan async / await, tangkap kegagalan dengan try / catch, bandingkan serial dengan Promise.all, dan lihat akibatnya kalau lupa menulis await.

Dengan then pada Promise, value yang datang hanya bisa kamu terima sebagai argumen sebuah fungsi. Kalau hasil pertama ingin kamu pakai di langkah ketiga, kamu harus membuat fungsi bersarang atau menyalin value-nya ke variabel di luar, dan kodenya jadi susah dibaca.

Artikel ini membahas async / await, yang menunggu hasilnya lalu lanjut ke baris berikutnya, serta cara menunggu beberapa Promise sekaligus.

Menulis Langkah dari Atas ke Bawah — Fungsi async dan await

Misalnya kamu memesan slot kosong di tempat parkir, lalu memasukkan nomor slot yang didapat ke dalam pesan. Kalau ditulis dengan then, semua proses yang memakai nomor slot harus ditaruh di dalam fungsi yang kamu berikan ke then, dan kode yang kamu tulis di bawahnya, di luar fungsi itu, sudah dijalankan sebelum nomornya datang.

Di dalam fungsi async (fungsi yang dideklarasikan dengan async dan selalu mengembalikan Promise saat dipanggil), kamu bisa memakai await. await hanya menghentikan sisa fungsi itu sampai Promise-nya selesai, lalu mengambil value fulfilled-nya. Selama berhenti, pemanggil langsung menerima Promise yang masih pending.

// Mengembalikan Promise yang fulfilled dengan value setelah ms milidetik (sama seperti artikel sebelumnya)
function delay(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}
const assignSpot = () => delay(100, "B2-17");

// Tulis await di dalam fungsi yang diberi async
async function showParking() {
  console.log("Mencari slot parkir kosong");   // Mencari slot parkir kosong
  const spot = await assignSpot();              // Tidak lanjut ke baris berikutnya sampai fulfilled
  console.log(`Slot ${spot} sudah dipesan`);    // Slot B2-17 sudah dipesan
  return `Kamu bisa parkir di slot ${spot}`;    // Promise yang dikembalikan menjadi fulfilled dengan pesan ini
}

// Pemanggil juga menunggu Promise yang dikembalikan dengan await
const message = await showParking();
console.log(message);                           // Kamu bisa parkir di slot B2-17
Yang Ditunggu oleh Masing-Masing await
Pemanggil — const message = await showParking()
  • showParking() berjalan sampai await pertama di dalamnya, lalu mengembalikan Promise yang masih pending
  • Eksekusi tidak sampai ke console.log(message) sebelum Promise itu fulfilled
Di dalam async function showParking()
  • Menampilkan Mencari slot parkir kosong
  • await assignSpot() — berhenti sampai fulfilled 100ms kemudian
  • Lanjut ke baris berikutnya setelah spot berisi "B2-17"
  • Pesan yang di-return membuat Promise yang dikembalikan menjadi fulfilled
await di dalam menunggu hasil assignSpot, await di luar menunggu fungsinya selesai. Value yang di-return masuk ke message.

Fungsi async tanpa return pun tetap mengembalikan Promise, yang fulfilled dengan undefined setelah isi fungsinya berjalan sampai akhir. Walaupun kamu tidak butuh value-nya, kalau pemanggilannya diberi await, baris berikutnya baru jalan setelah proses di dalam fungsi selesai.

await di Luar Fungsi Hanya Bisa di Tempat Tertentu

Lingkungan eksekusi di halaman ini membungkus seluruh kodemu dengan fungsi async, jadi kamu juga bisa menulis await di luar fungsi, seperti const message = await showParking();. Di file JavaScript biasa, await di luar fungsi async bisa menjadi SyntaxError. Kalau menulis kode di tempat lain, taruh await di dalam fungsi async.

Kirim notifikasi booking untuk sebuah salon. delay, confirmBooking, dan registerReminder sudah dideklarasikan.

① Ubah notifyBooking menjadi fungsi yang bisa menunggu hasil di dalamnya.

② Tunggu tanggal dan jam yang sudah dikonfirmasi, lalu tampilkan “Jadwal kamu: 14 September, 15:00”.

③ Daftarkan pengingat untuk tanggal dan jam itu, lalu tampilkan hasilnya.

④ Di luar fungsi, tunggu sampai fungsinya selesai, lalu tampilkan “Booking kamu sudah selesai”.

(Kalau berjalan dengan benar, penjelasan akan muncul.)

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Memperlakukan Kegagalan sebagai Exception — await dan try / catch

Saat kamu sudah memesan kursi konser lalu membayar dengan kartu, pembayarannya bisa saja gagal. Cara mengambil value dengan await tidak membentuk chain, jadi kegagalannya tidak bisa ditangkap dengan .catch di akhir seperti di artikel sebelumnya.

Kalau kamu throw di dalam fungsi async, Promise yang dikembalikan fungsi itu menjadi rejected. Saat kamu await Promise yang rejected, Error yang sama dilempar di baris await itu, jadi kamu cukup membungkusnya dengan try / catch.

// delay sama dengan kode di bagian pertama
const holdSeat = () => delay(100, "S-204");
const sendReceipt = (seatId) => delay(100, `Struk untuk ${seatId} sudah dikirim`);

// throw di dalam fungsi async membuat Promise yang dikembalikan menjadi rejected
async function payByCard() {
  await delay(100);                          // Hanya menunggu 100ms tanpa memakai value
  throw new Error("Kartu sudah kedaluwarsa");
}

try {
  const seatId = await holdSeat();
  console.log(`Kursi ${seatId} sudah diamankan`);       // Kursi S-204 sudah diamankan
  await payByCard();                                     // Rejected, jadi baris ini melempar exception
  const receipt = await sendReceipt(seatId);             // Baris dari sini ke bawah tidak dijalankan
  console.log(receipt);
} catch (error) {
  console.log(`Pembelian gagal: ${error.message}`);      // Pembelian gagal: Kartu sudah kedaluwarsa
}
Baris yang Jalan Berikutnya Saat Berhasil dan Gagal
Kalau pembayaranberhasilawait payByCard()selesaiLanjut ke barissendReceiptMengirimstrukKalau kartu sudahkedaluwarsaawait payByCard()melempar exceptionSisa trydilompaticatch (error)menampilkan pesan
Di jalur atas, eksekusi lanjut ke baris berikutnya. Di jalur bawah, sisa try dilompati dan eksekusi pindah ke catch. Ke mana eksekusi berlanjut ditentukan oleh status Promise yang di-await.

catch hanya menerima exception yang dilempar di dalam kurung kurawal try, jadi tulis baris await yang mungkin gagal di dalam try. Proses bersih-bersih yang harus jalan baik berhasil maupun gagal bisa langsung kamu tulis di finally setelah try / catch.

Perpanjang masa pinjam buku perpustakaan. delay, loans, dan renewLoan sudah dideklarasikan.

① Di requestRenewal, tunggu hasil renewLoan lalu tampilkan pesan yang kamu terima.

② Kalau gagal, tampilkan “Tidak bisa diperpanjang: ” diikuti pesan Error-nya.

③ Berhasil ataupun gagal, tutup dengan menampilkan baris seperti “Proses Dasar-Dasar Statistik selesai”.

④ Tunggu sampai buku pertama di loans selesai diproses, baru ajukan buku kedua.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Memulai Tiga Pemanggilan Sekaligus — Serial vs Promise.all

Misalnya saat halaman produk dibuka, kamu mengambil jumlah stok, jumlah ulasan, dan perkiraan tanggal pengiriman. Kalau kamu menulis satu await per baris, setiap request menunggu request sebelumnya selesai dulu baru dimulai, padahal tidak ada baris yang memakai hasil sebelumnya. Request yang tidak saling berhubungan pun jadi ikut mengantre.

Cara menjalankan tugas berikutnya setelah tugas sebelumnya selesai disebut serial, sedangkan cara memulai beberapa tugas sekaligus lalu menunggunya disebut paralel. Untuk menjalankannya secara paralel, panggil ketiga fungsi lebih dulu untuk mendapatkan Promise-nya, berikan ke Promise.all dari artikel sebelumnya, lalu tunggu dengan await, bukan then.

// delay sama dengan kode di bagian pertama
const loadStock = () => delay(120, 8);
const loadReviews = () => delay(50, 23);
const loadArrival = () => delay(90, "13 September");

// Serial: panggil berikutnya setelah hasil sebelumnya datang (lengkap setelah 120 + 50 + 90 = 260ms)
const stock = await loadStock();
const reviews = await loadReviews();
const arrival = await loadArrival();
console.log([stock, reviews, arrival].join(" / "));  // 8 / 23 / 13 September

// Paralel: panggil ketiganya dulu, lalu tunggu (lengkap setelah yang paling lambat, 120ms)
const promises = [loadStock(), loadReviews(), loadArrival()];
const results = await Promise.all(promises);
console.log(results.join(" / "));                   // 8 / 23 / 13 September (hasilnya sama)
Urutan Datangnya Hasil: Serial vs Paralel
Serial 0mspanggil stok120ms stok tibapanggil ulasan170ms ulasan tibapanggil tanggal260msketiganya lengkapParalel 0mspanggil ketiganya50msulasan tiba90mstanggal tiba120ms stok tibaketiganya lengkap
Di jalur bawah, ketiganya dimulai di 0ms dan lengkap di 120ms, waktu yang paling lambat. Dengan satu await per baris, total waktu tunggunya 260ms.

Kalau kamu menulis await di dalam isi for...of, element array juga diproses secara serial, satu per satu, dan total waktu tunggunya adalah jumlah waktu semua element. Tabel di bawah merangkum kapan memilih serial dan kapan memilih paralel.

SituasiCara menulisAlasan
Ambil stok, jumlah ulasan, tanggal kirimawait Promise.all (paralel)Tak saling memakai hasil, bisa mulai bersamaan
Kirim email konfirmasi setelah dapat nomor pesananSatu await per baris (serial)Pemanggilan kedua butuh hasil pemanggilan pertama
Proses element array satu per satuawait di dalam isi for...of (serial)Kalau paralel, urutan selesai bisa beda dari array

Sebuah aplikasi cuaca mengambil prakiraan untuk tiga kota. Bandingkan urutan datangnya. delay, loadForecast, dan cities sudah dideklarasikan.

① Ikuti urutan cities, lalu tunggu prakiraan tiap kota datang sebelum mengambil kota berikutnya.

② Tampilkan “Prakiraan diambil satu per satu”.

③ Kali ini, mulai ambil ketiga kota sekaligus, lalu tunggu sampai semuanya lengkap.

④ Tampilkan prakiraannya dengan digabung memakai “ / ”.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Memanggil Tanpa await — Variabel Berisi Promise

Misalnya kamu menampilkan saldo poin member dan menawarkan hadiah kalau poinnya 1000 atau lebih. Kalau kamu lupa menulis await saat memanggil fungsi async, tidak ada SyntaxError. Yang berubah hanya teks yang tampil dan hasil perbandingannya, sehingga susah menemukan di mana letak kesalahannya.

Fungsi async tidak mengembalikan value yang kamu return, tetapi Promise yang fulfilled dengan value itu. Kalau hasilnya kamu terima tanpa await, variabelnya berisi Promise, dan kalau disisipkan ke template literal, yang tercetak adalah [object Promise] (bentuk Promise saat diubah menjadi string).

// delay sama dengan kode di bagian pertama
async function loadPoints() {
  await delay(100);
  return 1200;                              // Promise yang dikembalikan menjadi fulfilled dengan 1200
}

// Kalau lupa menulis await, points berisi Promise
const points = loadPoints();
console.log(`Saldo poin: ${points}`);       // Saldo poin: [object Promise]
console.log(points >= 1000);                // false (Promise menjadi NaN saat diubah ke angka, jadi perbandingannya gagal)

// Dengan await, variabelnya berisi value fulfilled
const balance = await loadPoints();
console.log(`Saldo poin: ${balance}`);      // Saldo poin: 1200
console.log(balance >= 1000);               // true
points Tetap Berisi Promise
points =loadPoints()Berisi Promiseyang pendingFulfilledsetelah 100mspoints tetapberisi PromiseDisisipkanke stringTercetak[object Promise]
Walaupun sudah fulfilled, isi points tidak pernah berganti menjadi 1200. Untuk mendapatkan value-nya, kamu perlu await.

Kalau output-nya menampilkan [object Promise], atau kondisi yang seharusnya terpenuhi malah bernilai false, cek apakah pemanggilan fungsi async-nya sudah memakai await. Kalau ditaruh sebagai kondisi if, Promise adalah object sehingga bernilai truthy, dan kondisinya selalu terpenuhi apa pun value yang dikembalikan fungsinya.

Kegagalan Tanpa await Tidak Sampai ke catch

Di dalam try, kalau kamu memanggil fungsi async yang mengembalikan Promise rejected tanpa await, baris itu tidak melempar exception dan catch tidak pernah dijalankan. Kegagalannya tidak ditangani di mana pun, dan lingkungan eksekusi di halaman ini tidak menampilkan apa-apa. Supaya bisa diterima di catch, tambahkan await pada pemanggilannya.

Cek apakah kupon sudah kedaluwarsa, lalu beri tahu kupon mana yang bisa dipakai. delay, coupons, dan isExpired sudah dideklarasikan.

① Tanpa await, panggil isExpired dengan kupon pertama dan jadikan hasilnya kondisi if.

② Kalau kondisinya terpenuhi, tampilkan “AUTUMN10 sudah kedaluwarsa”; kalau tidak, tampilkan “AUTUMN10 bisa dipakai”.

③ Telusuri coupons secara berurutan, tunggu hasil tiap kupon, lalu lakukan pengecekan yang sama dan tampilkan pesannya.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Di dalam try, apa yang terjadi pada baris-baris di bawah baris yang melakukan await pada Promise yang menjadi rejected?

Soal 2Kalau kamu melakukan await pada tiga pemanggilan yang masing-masing butuh 70ms, 30ms, dan 50ms, satu per baris, kira-kira berapa lama sampai ketiganya lengkap?

Soal 3Kalau kamu memanggil fungsi async yang diakhiri return 1200; tanpa await lalu menyimpan hasilnya ke variabel, apa isi variabel itu?