Menu

async/await JavaScript: Kode Async yang Terbaca dari Atas ke Bawah

Cara kerja async/await yang sebenarnya di JavaScript: fungsi async, menunggu promise, menangani error dengan try/catch, dan menjalankan tugas secara paralel.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

async/await Hanyalah Promise yang Menyamar

async/await bukan model konkurensi baru. Ini adalah sintaks pemanis di atas promise yang memungkinkanmu menulis kode yang terlihat berurutan walaupun sebenarnya asynchronous. Mesinnya sama, bentuknya jauh lebih bersahabat.

Berikut tugas yang sama ditulis dengan dua cara:

Kedua fungsi mengembalikan promise. Keduanya melakukan hal yang persis sama. Versi async terbaca dari atas ke bawah tanpa rangkaian .then, dan itulah daya tarik utamanya.

async Menandai Fungsi yang Mengembalikan Promise

Taruh async di depan function, arrow function, atau method, maka dua hal terjadi:

  1. Fungsi itu selalu mengembalikan promise. Apa pun yang kamu return menjadi nilai hasilnya.
  2. Kamu boleh memakai await di dalamnya.

Perhatikan bahwa result bukan string, melainkan promise yang menghasilkan string tersebut. Walaupun greet tidak berisi await dan tidak melakukan pekerjaan async apa pun, kata kunci async tetap membungkus nilai kembaliannya dalam promise. Kalau fungsinya melempar error, promise-nya ditolak.

await Menjeda Sampai Promise Selesai

Di dalam fungsi async, await somePromise menjeda fungsi itu sampai promise-nya berhasil, lalu memberimu nilai hasilnya. Kalau promise-nya ditolak, await melempar error.

Perhatikan urutan output-nya. "started countdown" tercetak sebelum "2", karena await hanya menjeda fungsi async, bukan seluruh program. Event loop terus berjalan; countdown baru dilanjutkan nanti setiap kali promise wait selesai.

Kamu bisa await apa pun yang mirip promise. await 42 juga sah: nilai yang bukan promise dibungkus dalam Promise.resolve(42) dan langsung selesai.

Menangani Error dengan try/catch

Dengan promise biasa, kamu merangkai .catch(). Dengan async/await, promise yang ditolak menjadi exception yang dilempar dan bisa kamu tangkap dengan cara biasa:

Satu try/catch mencakup setiap await di dalamnya. Kegagalan jaringan, error saat parsing JSON, dan pernyataan throw milikmu sendiri semuanya berakhir di catch yang sama. Itu peningkatan nyata dibandingkan rangkaian .then/.catch yang bersarang.

Satu hal yang perlu diwaspadai: fetch hanya ditolak saat ada error jaringan, bukan saat HTTP 4xx/5xx. Kamu harus mengecek res.ok sendiri lalu melempar error, pola yang akan terus kamu lihat di kode sungguhan.

Jangan await di Dalam Loop Kalau Tidak Perlu

Ini jebakan async/await yang paling umum. await berurutan di dalam loop berarti setiap iterasi menunggu iterasi sebelumnya:

sequential butuh sekitar 900ms. parallel butuh sekitar 300ms. Pegangan praktisnya: kalau tugas-tugasnya tidak saling bergantung pada hasil masing-masing, mulai semuanya, lalu await Promise.all. Lakukan await satu per satu hanya kalau panggilan berikutnya benar-benar butuh hasil sebelumnya.

Untuk koleksi, pola yang lazim adalah Promise.all(items.map(async (x) => ...)). for...of biasa dengan await di dalamnya berjalan satu per satu. Kadang kamu memang menginginkannya (membatasi laju, menjaga urutan), tetapi biasanya tidak.

Mencampur async/await dan Promise Biasa

Kamu tidak harus memilih salah satu. Fungsi async mengembalikan promise, dan await bekerja pada promise apa pun, jadi kamu bisa mencampurnya dengan bebas:

Kedua gaya bisa saling menggantikan. Pakai await saat kode lebih enak dibaca dari atas ke bawah; pakai .then saat kamu butuh sesuatu yang cepat dan sekali pakai, atau saat kamu bekerja di luar konteks async.

Top-Level await (di ES Module)

Dulu, kamu harus membungkus await dalam fungsi async karena await tidak diizinkan di tingkat atas sebuah script. Sekarang sudah berubah: di dalam ES module (file .mjs atau <script type="module">), kamu bisa langsung await di tingkat atas:

// in an ES module
const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
const user = await res.json();
console.log(user.name);

Top-level await menunda selesainya module sampai promise yang ditunggu selesai, dan setiap module yang meng-import module itu ikut menunggu. Berguna untuk memuat konfigurasi dan import dinamis, tetapi pakai seperlunya: top-level await yang lambat memblokir semua yang meng-import module tersebut.

Di file CommonJS atau script inline biasa, cara ini tetap gagal dengan SyntaxError. Solusi klasiknya adalah fungsi async yang langsung dipanggil:

Hal yang Sering Menjebak

Tur singkat jebakan yang biasa terjadi:

  • Lupa async. Memakai await di fungsi biasa adalah syntax error. Solusinya menambahkan async, atau memanggil helper async dengan .then.
  • Lupa melakukan await pada hasilnya. const data = getJSON(url); memberimu promise, bukan datanya. Kalau kamu memakainya seolah-olah itu nilainya, kamu akan melihat [object Promise] muncul di output-mu.
  • Penolakan yang tidak ditangani. Fungsi async yang kamu jalankan lalu lupakan (doWork();) akan diam-diam menelan error, kecuali kamu menambahkan .catch atau melakukan await di dalam try/catch.
  • forEach dengan callback async. array.forEach(async (x) => await something(x)) tidak menunggu apa pun, karena forEach mengabaikan promise yang dikembalikan. Pakai for...of dengan await, atau Promise.all(array.map(...)).

Jalankan kodenya: "finished?" tercetak sebelum "done" mana pun, karena broken selesai tanpa menunggu. fixed menunggu semuanya, lalu mencetak "finished!" paling akhir.

Kapan Memakai async/await

Jadikan async/await pilihan default untuk kode apa pun yang melakukan lebih dari satu langkah asynchronous secara berurutan, atau yang butuh penanganan error ala try/catch. Tetap pakai promise mentah untuk kode satu baris yang sederhana, untuk kode library yang mengembalikan promise tanpa perlu menunggu apa pun sendiri, atau saat kamu benar-benar butuh combinator seperti Promise.race atau .finally() dalam sebuah rangkaian.

Kalau dipakai dengan baik, async/await membuat kode async terbaca seperti resep: lakukan ini, lalu ini, lalu ini. Event loop tetap bekerja seperti biasa, hanya saja kamu tidak perlu lagi berpikir dalam callback.

Berikutnya: fetch API

Sebagian besar contoh di sini memakai fetch sebagai pengganti "sesuatu yang async". Ia layak dibahas dengan serius: cara kerja request dan response, menangani JSON, mengatur header, dan mengapa fetch tidak ditolak saat ada error HTTP. Itu topik halaman berikutnya.

Pertanyaan yang sering diajukan

Apa fungsi async/await di JavaScript?

async/await adalah sintaks untuk bekerja dengan promise yang memungkinkanmu menulis kode asynchronous seolah-olah synchronous. async menandai fungsi yang mengembalikan promise, dan await menjeda fungsi itu sampai sebuah promise selesai, lalu memberimu nilai hasilnya. Di baliknya tetap promise, hanya lebih mudah dibaca.

Bisakah aku memakai await di luar fungsi async?

Di tingkat atas sebuah ES module, bisa. Itu disebut top-level await. Di dalam fungsi biasa atau script CommonJS, tidak bisa: await di luar fungsi async adalah syntax error. Solusinya biasanya membungkus kode dalam fungsi async lalu memanggilnya, atau mengubah file menjadi ES module.

Bagaimana cara menangani error dengan async/await?

Bungkus panggilan yang di-await dengan try/catch. Setiap promise yang ditolak dan kamu await berubah menjadi exception yang dilempar dan bisa ditangani blok catch. Untuk tugas latar belakang yang tidak kamu await, pasang .catch() pada promise yang dikembalikan agar penolakannya tidak lolos tanpa ditangani.

Apakah await memblokir seluruh program?

Tidak. await hanya menjeda fungsi async yang sedang berjalan. Event loop terus berjalan: timer tetap berbunyi, tugas async lain tetap berjalan, dan UI tetap responsif. Kode pemanggil menerima promise yang masih pending dan langsung melanjutkan.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai