async/await

Kode asinkron yang tampak berurutan dan penanganan error dengan try/catch.

Daftar isi

Rantai then yang panjang berhasil menghindari bersarang, tetapi alurnya tetap terbaca agak terbalik, karena hasil pekerjaan pertama justru dicari di tautan-tautan berikutnya. Kata kunci async dan await menyusun ulang kode asinkron agar terlihat seperti urutan biasa, dari atas ke bawah, lengkap dengan try/catch yang sudah lama dikenal. Topik ini membahas nilai balik fungsi async, perilaku await, penanganan errornya, lalu menimbang kapan pekerjaan perlu diurutkan dan kapan sebaiknya dijalankan bersamaan.

Fungsi async dan nilai baliknya#

Menambahkan async di depan sebuah fungsi mengubah perilaku nilai baliknya secara permanen. Fungsi async selalu mengembalikan promise. Nilai yang dikembalikan dengan return dibungkus menjadi nilai terpenuhi, sedangkan throw di dalam tubuhnya menjadi penolakan. Pemanggilnya tidak perlu menebak bentuk nilai baliknya, selalu ada promise yang bisa diberi then atau di-await.

js nilai-balik.js
// Fungsi async selalu mengembalikan promise, tanpa perlu new Promise.
async function ambilAngka() {
  return 42;
}

console.log("tipe nilai balik:", typeof ambilAngka());

ambilAngka().then((nilai) => {
  console.log("nilai di dalam then:", nilai);
});
text Keluaran
tipe nilai balik: object
nilai di dalam then: 42

Keluarannya membuktikan klaim itu. Angka 42 yang dikembalikan fungsi sampai ke then sebagai nilai terpenuhi, dan tipe nilai balik pemanggilannya berupa object, yaitu promise. Penanda async juga berlaku pada arrow function dan fungsi ekspresi, misalnya const ambil = async () => 42, sehingga gaya penulisannya fleksibel mengikuti kode di sekitarnya. Di dalam tubuhnya, return juga sering didahului await, sehingga fungsi yang berisi pekerjaan asinkron tetap menghasilkan promise yang bisa dirangkai.

await dan urutan yang tampak berurutan#

Kata kunci await hanya boleh dipakai di dalam fungsi async, dan tugasnya menunggu promise di sebelah kanannya selesai. Selama menunggu, yang dihentikan hanya fungsi asyncnya, sehingga baris berikutnya di dalam fungsi itu tertunda. Thread utama tetap bebas melayani pekerjaan lain. Begitu promise selesai, eksekusi bersambung dan nilai promise itu diteruskan ke variabel di sebelah kiri.

js langkah-await.js
// await menghentikan sementara jalannya fungsi async sampai promise selesai.
function tunggu(ms, label) {
  return new Promise((resolve) => setTimeout(() => resolve(label), ms));
}

async function jalankanLangkah() {
  const a = await tunggu(20, "langkah pertama");
  console.log(a);
  const b = await tunggu(20, "langkah kedua");
  console.log(b);
  return "semua langkah selesai";
}

jalankanLangkah().then((pesan) => console.log(pesan));
console.log("kode di luar fungsi async tetap berjalan");
text Keluaran
kode di luar fungsi async tetap berjalan
langkah pertama
langkah kedua
semua langkah selesai

Baris “kode di luar fungsi async tetap berjalan” tercetak paling awal, bukti bahwa await di dalam jalankanLangkah tidak membekukan seluruh program. Di dalam fungsinya sendiri, alurnya terbaca seperti kode sinkron, tunggu langkah pertama, cetak, tunggu langkah kedua, cetak, lalu kembalikan pesan penutup.

Penanganan error dengan try/catch#

Kegagalan promise yang di-await berperilaku seperti error biasa, yaitu dilempar kembali pada baris await-nya. Akibatnya penanganan error asinkron dapat memakai try/catch yang sama dengan kode sinkron, tanpa catch terpisah di ujung rantai. Blok finally pun berlaku seperti biasa, selalu berjalan untuk pembersihan.

js try-catch-await.js
// Error dari promise yang di-await dapat ditangkap try/catch biasa.
async function bagi(a, b) {
  if (b === 0) {
    throw new Error("pembagi tidak boleh nol");
  }
  return a / b;
}

async function utama() {
  try {
    const hasil = await bagi(8, 0);
    console.log("hasil:", hasil);
  } catch (err) {
    console.log("tertangkap:", err.message);
  } finally {
    console.log("perhitungan selesai");
  }
}

utama();
text Keluaran
tertangkap: pembagi tidak boleh nol
perhitungan selesai

Pernyataan throw di dalam fungsi async bagi menjadi penolakan promise-nya, dan penolakan itu muncul kembali pada baris await di dalam blok try. Jalur catch menampungnya dengan pesan yang sama seperti penanganan error sinkron, lalu finally menutup dengan baris pembersihan. Bentuk ini setara dengan .catch() pada rantai then, hanya saja seluruh alurnya berada di dalam satu blok yang terbaca dari atas ke bawah.

Berurutan atau paralel#

Menulis beberapa await satu per satu menghasilkan alur berurutan, pekerjaan kedua baru dimulai setelah pekerjaan pertama selesai. Bila kedua pekerjaan tidak saling bergantung, urutan seperti itu hanya membuang waktu. Total penundaannya menjadi jumlah keduanya. Pilihan yang lebih baik adalah menjadwalkan keduanya bersamaan, lalu menunggu gabungannya dengan Promise.all.

js berurutan-vs-paralel.js
// Menunggu satu per satu dan menjalankan bersamaan menghasilkan alur berbeda.
function tunggu(ms, label) {
  return new Promise((resolve) => setTimeout(() => resolve(label), ms));
}

async function berurutan() {
  console.log("mulai A");
  await tunggu(20, "A");
  console.log("A selesai");
  console.log("mulai B");
  await tunggu(20, "B");
  console.log("B selesai");
}

async function paralel() {
  const janjiA = tunggu(20, "A").then(() => console.log("A selesai"));
  console.log("A dan B dijadwalkan bersamaan");
  const janjiB = tunggu(20, "B").then(() => console.log("B selesai"));
  await Promise.all([janjiA, janjiB]);
  console.log("keduanya selesai");
}

berurutan().then(() => {
  console.log("---");
  return paralel();
});
text Keluaran
mulai A
A selesai
mulai B
B selesai
---
A dan B dijadwalkan bersamaan
A selesai
B selesai
keduanya selesai

Pada alur berurutan, “mulai B” baru tercetak setelah “A selesai”, sedangkan pada alur paralel kedua timer berjalan bersamaan dan kedua baris selesainya menyusul hampir serentak. Dengan penundaan 20 milidetik masing-masing, alur berurutan menunggu sekitar 40 milidetik, sedangkan alur paralel cukup menunggu pekerjaan yang paling lambat, sekitar 20 milidetik. Kegagalan salah satu pekerjaan pada alur paralel membuat Promise.all menolak, dan penolakan itu dilempar pada baris await-nya, sehingga satu try/catch di sekelilingnya cukup untuk menangkap semuanya. Peragaan berikut memperlihatkan kedua alurnya di browser, tekan tombolnya dan perhatikan jeda di antara baris-barisnya.

Kode sumber demo
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <title>Demo berurutan vs paralel</title>
  <style>
    body { font-family: "Segoe UI", Arial, sans-serif; margin: 14px; font-size: 14px; color: #1f2937; }
    button { padding: 6px 12px; margin-right: 8px; margin-bottom: 10px; cursor: pointer; }
    pre { font-size: 13px; line-height: 1.7; background: #f3f4f6; padding: 10px; min-height: 140px; }
  </style>
</head>
<body>
  <p>Pilih salah satu tombol, lalu perhatikan jeda di antara baris-barisnya.</p>
  <button id="tombol-berurutan" type="button">Jalankan berurutan</button>
  <button id="tombol-paralel" type="button">Jalankan paralel</button>
  <pre id="log"></pre>
  <script src="index.js"></script>

<script>
// Demo dua alur, menunggu satu per satu dan menjalankan bersamaan.

const log = document.getElementById("log");

function catat(pesan) {
  log.textContent = log.textContent + pesan + "\n";
}

function tunggu(ms, label) {
  return new Promise((resolve) => setTimeout(() => resolve(label), ms));
}

async function berurutan() {
  log.textContent = "";
  catat("mulai A");
  await tunggu(500, "A");
  catat("A selesai");
  catat("mulai B");
  await tunggu(500, "B");
  catat("B selesai");
}

async function paralel() {
  log.textContent = "";
  const janjiA = tunggu(500, "A").then(() => catat("A selesai"));
  catat("A dan B dijadwalkan bersamaan");
  const janjiB = tunggu(500, "B").then(() => catat("B selesai"));
  await Promise.all([janjiA, janjiB]);
  catat("keduanya selesai");
}

document.getElementById("tombol-berurutan").addEventListener("click", berurutan);
document.getElementById("tombol-paralel").addEventListener("click", paralel);

</script>
<script>
// Demo dua alur, menunggu satu per satu dan menjalankan bersamaan.

const log = document.getElementById("log");

function catat(pesan) {
  log.textContent = log.textContent + pesan + "\n";
}

function tunggu(ms, label) {
  return new Promise((resolve) => setTimeout(() => resolve(label), ms));
}

async function berurutan() {
  log.textContent = "";
  catat("mulai A");
  await tunggu(500, "A");
  catat("A selesai");
  catat("mulai B");
  await tunggu(500, "B");
  catat("B selesai");
}

async function paralel() {
  log.textContent = "";
  const janjiA = tunggu(500, "A").then(() => catat("A selesai"));
  catat("A dan B dijadwalkan bersamaan");
  const janjiB = tunggu(500, "B").then(() => catat("B selesai"));
  await Promise.all([janjiA, janjiB]);
  catat("keduanya selesai");
}

document.getElementById("tombol-berurutan").addEventListener("click", berurutan);
document.getElementById("tombol-paralel").addEventListener("click", paralel);

</script></body>
</html>

Dibandingkan dengan rantai then#

Fungsi async tidak menggantikan promise, ia hanya membungkusnya dengan sintaks yang lebih nyaman. Di balik layar, await tetap memakai promise dan then, sehingga kedua gaya dapat dicampur tanpa risiko. Program berikut menjalankan alur yang sama dengan kedua gaya sekaligus agar perbedaannya hanya terletak pada bentuk tulisannya.

js dua-gaya.js
// Dua gaya untuk alur yang sama, rantai then dan async/await.
function tunggu(ms, label) {
  return new Promise((resolve) => setTimeout(() => resolve(label), ms));
}

function denganThen() {
  return tunggu(20, "data").then((label) => label + " diolah");
}

async function denganAwait() {
  const label = await tunggu(20, "data");
  return label + " diolah";
}

denganThen().then((hasil) => console.log("gaya then:", hasil));
denganAwait().then((hasil) => console.log("gaya await:", hasil));
text Keluaran
gaya then: data diolah
gaya await: data diolah

Hasilnya identik karena keduanya benar-benar menjalankan mekanisme yang sama. Gaya await menang ketika alurnya panjang dan bercabang, karena variabel antaranya terbaca seperti kode biasa, sedangkan rantai then masih nyaman untuk alur pendek satu-dua tautan. Konsistensi lebih penting daripada pilihannya. Satu fungsi sebaiknya tidak berganti gaya di tengah jalan.

Ringkasan#

  • Fungsi async selalu mengembalikan promise, nilai baliknya dibungkus menjadi nilai terpenuhi dan throw menjadi penolakan.
  • await menghentikan sementara fungsi asyncnya saja, thread utama tetap melayani pekerjaan lain.
  • Penolakan dari await dilempar ulang pada barisnya, sehingga try/catch/finally biasa menangani error asinkron.
  • Pekerjaan yang bergantung diurutkan dengan await, sedangkan pekerjaan mandiri dijadwalkan bersamaan dan ditunggu lewat Promise.all.

Referensi#