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:
- Fungsi itu selalu mengembalikan promise. Apa pun yang kamu
returnmenjadi nilai hasilnya. - Kamu boleh memakai
awaitdi 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. Memakaiawaitdi fungsi biasa adalah syntax error. Solusinya menambahkanasync, atau memanggil helper async dengan.then. - Lupa melakukan
awaitpada 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.catchatau melakukanawaitdi dalamtry/catch. forEachdengan callback async.array.forEach(async (x) => await something(x))tidak menunggu apa pun, karenaforEachmengabaikan promise yang dikembalikan. Pakaifor...ofdenganawait, atauPromise.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.