Ilustrasi konsep asynchronous JavaScript

Ini episode yang paling sering bikin pemula menyerah di tengah jalan, padahal konsepnya sederhana kalau dijelaskan dengan analogi yang tepat. Setelah episode #47 soal module dan NPM, sekarang kita bahas async JavaScript: Promise dan async await.

Kenapa topik ini wajib? Karena hampir semua hal menarik di web itu butuh waktu: mengambil data dari server, membaca file, menunggu user. Kalau JavaScript berhenti total setiap kali menunggu, websitemu akan macet (freeze) setiap ada loading. Async adalah solusi JavaScript supaya program tetap responsif sambil menunggu.

Analoginya: Pesan Makanan di Warteg

Bayangkan kamu pesan makanan di warteg yang ramai. Ada dua cara melayani:

Cara sinkron (blocking): kamu pesan, lalu berdiri mematung di depan kasir sampai makananmu jadi. Antrian di belakangmu menunggu. Semua orang diam. Tidak efisien, kan?

Cara asinkron (non-blocking): kamu pesan, dapat nomor antrean, lalu duduk santai main HP. Saat makanan jadi, nomor kamu dipanggil. Selama menunggu, kasir melayani pelanggan lain. Semua orang tetap bergerak.

JavaScript bekerja dengan cara kedua. Saat kamu meminta data dari server, JavaScript tidak mematung menunggu. Ia memberimu sebuah Promise (ibarat nomor antrean: janji bahwa hasilnya akan datang), lalu melanjutkan kerjaan lain. Saat datanya tiba, kode yang menunggu janji itu dijalankan.

Promise punya tiga status, persis seperti nomor antreanmu:

  • pending: masih menunggu, makanan belum jadi.
  • fulfilled: berhasil, makanan datang. Nilainya bisa dipakai lewat .then().
  • rejected: gagal, misalnya bahan habis. Errornya ditangkap lewat .catch().
// Simulasi janji yang butuh 2 detik
function masakMie() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const mieHabis = false;
      if (mieHabis) {
        reject("Maaf, mie habis!");
      } else {
        resolve("Mie goreng spesial sudah jadi!");
      }
    }, 2000);
  });
}

masakMie()
  .then(pesan => console.log(pesan))   // kalau berhasil
  .catch(err => console.log(err));     // kalau gagal

console.log("Sambil menunggu, aku bisa ngobrol dulu.");

Berhenti dan tebak dulu! Urutan apa yang tercetak di console dari kode di atas?

Jawaban: "Sambil menunggu..." tercetak DULUAN, baru 2 detik kemudian "Mie goreng spesial sudah jadi!". Inilah inti async: masakMie() langsung mengembalikan Promise (nomor antrean) tanpa menunggu 2 detik. Baris console.log di bawahnya jalan terus. Callback di .then() baru dieksekusi setelah janjinya terpenuhi. Kalau kamu mengira urutannya dari atas ke bawah seperti kode biasa, di sinilah letak jebakan mental yang harus diperbaiki.

Callback Hell dan Kenapa Kita Butuh yang Lebih Baik

Sebelum Promise populer, programmer memakai callback: function yang dilempar sebagai argumen untuk dijalankan nanti. Masalahnya, kalau ada 4-5 langkah berurutan (login, ambil profil, ambil postingan, ambil komentar), kodenya menjorok ke dalam seperti piramida. Ini dijuluki callback hell atau "piramida kutukan".

Promise memperbaikinya dengan chaining: tiap .then() mengembalikan Promise baru, jadi bisa disambung ke bawah, bukan ke dalam. Tapi masih terasa seperti rantai yang panjang. Maka lahirlah async await: cara menulis kode async yang terlihat seperti kode biasa dari atas ke bawah.

// Dengan async/await: terlihat seperti kode sinkron biasa
async function pesanMakanan() {
  try {
    const mie = await masakMie();      // tunggu sampai jadi
    console.log(mie);
    const minum = await buatkanTeh();  // baru lanjut ke langkah berikut
    console.log(minum);
  } catch (err) {
    console.log("Gagal pesan:", err);  // semua error ditangkap di sini
  }
}

Tiga aturan main async await yang wajib dihafal:

  1. await hanya boleh dipakai di dalam function async. Ini aturan keras bahasa.
  2. await menjeda function itu saja, bukan seluruh program. Kode lain di luar function tetap jalan. Ini bedanya dengan "freeze".
  3. Function async selalu mengembalikan Promise, walaupun kamu me-return nilai biasa. Jadi pemanggilnya tetap perlu await atau .then().

Berhenti dan tebak dulu! Apa output kode ini?

async function dapatAngka() {
  return 42;
}
const hasil = dapatAngka();
console.log(hasil);
Jawaban: Promise { 42 } (object Promise, bukan angka 42). Ini aturan nomor 3: function async SELALU membungkus return value menjadi Promise. Untuk mendapatkan angka 42-nya, kamu harus await dapatAngka() atau dapatAngka().then(h => ...). Banyak bug pemula berasal dari lupa membungkus ini: mereka mengira dapat nilai langsung, padahal dapat "nomor antrean".

Kuis cepat: Manakah pernyataan yang BENAR tentang async await?

Persepsi yang Perlu Diluruskan

Persepsi salah 1: "Pakai async await berarti kodenya jadi sinkron dan blocking." Salah total. await hanya menjeda function itu, program lain tetap jalan. Buktinya ada di Tebak Dulu pertama tadi: baris di luar function tetap dieksekusi duluan.

Persepsi salah 2: "Error di kode async tidak perlu ditangani khusus." Justru sebaliknya. Promise yang rejected tapi tidak ada .catch() atau try catch akan jadi error misterius yang sulit dilacak. Biasakan selalu pasang penangkap error, seperti pramusaji yang selalu siap kalau pesanan gagal.

Latihan: Simulasi Dapur Asinkron

Tulis program kecil yang mensimulasikan tiga pesanan warteg dengan waktu masak berbeda (pakai setTimeout dalam Promise, misalnya 1 detik, 2 detik, 3 detik). Lalu:

  1. Jalankan ketiganya berurutan dengan await satu per satu, catat total waktunya (harusnya sekitar 6 detik).
  2. Ubah kodenya supaya ketiganya dimasak bersamaan memakai Promise.all([pesan1, pesan2, pesan3]). Catat total waktunya sekarang (harusnya sekitar 3 detik, mengikuti yang paling lama).
  3. Renungkan: kapan harus berurutan dan kapan boleh bersamaan? (Petunjuk: kalau langkah B butuh hasil langkah A, wajib berurutan.)

Latihan ini melatih intuisi terpenting programmer async: membedakan yang harus antre dan yang boleh paralel. Di episode berikutnya, kita pakai semua ilmu ini untuk proyek nyata: Fetch API dan Aplikasi Cuaca, aplikasi pertamamu yang mengambil data sungguhan dari internet. Sampai jumpa.