Javascript Event Loop Async Await Promise
Selesai membaca dokumentasi ini?
Kembali ke rujukan utama JavaScript untuk melanjutkan topik lainnya.
Kembali ke rujukan utama JavaScript untuk melanjutkan topik lainnya.
Event loop adalah mekanisme yang membuat JavaScript bisa menangani pekerjaan asinkron tanpa memblokir eksekusi utama. Di browser maupun Node.js, JavaScript tetap menjalankan satu call stack utama, lalu event loop mengatur kapan callback dari task queue dan microtask queue boleh masuk ke stack.
Promise adalah representasi hasil operasi asinkron yang belum selesai, sudah selesai, atau gagal. Promise callback lewat then(), catch(), dan finally() dijadwalkan sebagai microtask, bukan task biasa.
async/await adalah sintaks di atas Promise. await menghentikan eksekusi fungsi async secara sementara, lalu melanjutkannya lagi setelah Promise yang ditunggu selesai. Secara praktik, await tetap bekerja lewat Promise dan microtask queue.
Banyak bug async bukan berasal dari Promise yang salah, tapi dari salah paham urutan eksekusi. Contohnya:
console.log() tampil dalam urutan yang tidak didugatry/catch tidak menangkap Promise rejection karena konteksnya salahKalau urutan call stack, task queue, dan microtask queue tidak dipahami, debugging async biasanya jadi tebak-tebakan.
Call stack adalah tempat JavaScript mengeksekusi fungsi yang sedang berjalan.
Task queue menyimpan task makro seperti:
setTimeout() dan setInterval()Task di queue ini tidak masuk ke stack sebelum stack kosong dan microtask queue sudah dikuras.
Microtask queue menyimpan pekerjaan yang diprioritaskan lebih tinggi daripada task biasa.
Contoh isi microtask queue:
Promise.then()Promise.catch()Promise.finally()awaitqueueMicrotask()Aturannya penting: setelah satu task selesai, JavaScript menguras semua microtask yang ada sebelum lanjut ke task berikutnya.
Urutan sederhananya begini:
Intinya:
setTimeout(..., 0)Callback dari Promise tidak dijalankan saat Promise dibuat. Callback dijadwalkan ketika Promise berubah status dan ada handler yang terpasang.
console.log('1');
Promise.resolve().then(() => {
console.log('2');
});
console.log('3');Urutan output:
1
3
2Penjelasannya:
console.log('1') langsung masuk call stack.Promise.resolve().then(...) tidak langsung mengeksekusi callback; callback masuk microtask queue.console.log('3') tetap jalan dulu karena masih synchronous.2 muncul terakhir.const p = Promise.resolve('done');
p.then(value => {
console.log(value);
});
console.log('after');Output:
after
doneWalaupun Promise-nya sudah resolved, handler .then() tetap dijalankan sebagai microtask, bukan langsung di baris itu juga.
async membuat fungsi selalu mengembalikan Promise.
await memecah eksekusi fungsi async menjadi dua fase:
awaitasync function run() {
console.log('A');
await Promise.resolve();
console.log('B');
}
console.log('C');
run();
console.log('D'Urutan output:
C
A
D
Bconsole.log('C') berjalan dulu.run() dipanggil dan masuk ke call stack.console.log('A') berjalan synchronously di dalam fungsi async.await Promise.resolve() menghentikan kelanjutan fungsi dan menjadwalkan sisa fungsi ke microtask queue.console.log('D') tetap jalan sebelum microtask dieksekusi.console.log('B') jalan dari microtask queue.| Sumber callback | Queue | Prioritas |
|---|---|---|
Promise.then() | microtask | lebih tinggi |
Promise.catch() | microtask | lebih tinggi |
Promise.finally() | microtask | lebih tinggi |
await continuation | microtask | lebih tinggi |
setTimeout() | task queue | lebih rendah |
| event click/input | task queue | lebih rendah |
console.log('start');
setTimeout(() => {
console.log('timeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise');
Output:
start
end
promise
timeoutsetTimeout(..., 0) jalan paling cepatsetTimeout(..., 0) bukan berarti langsung saat itu juga. Callback tetap menunggu giliran di task queue.
Kalau ada microtask di depan, microtask akan selesai dulu.
then() berjalan sinkronthen() selalu menjadwalkan callback. Ia tidak dieksekusi langsung pada baris yang sama.
Ini sering bikin log kelihatan "loncat".
try/catch tidak menangkap rejection di luar konteks awaittry {
Promise.reject(new Error('gagal'));
} catch (err) {
console.log('tidak selalu tertangkap');
}Kalau Promise tidak di-await atau tidak di-catch, error bisa lolos dari try/catch biasa karena rejection terjadi asinkron.
Pola yang benar:
try {
await Promise.reject(new Error('gagal'));
} catch (err) {
console.log('tertangkap');
}await hanya menunggu Promise di depannyaawait tidak memblokir event loop. Ia hanya menunda kelanjutan fungsi async itu sendiri.
Artinya:
awaitKalau microtask terus menambahkan microtask baru, task queue bisa tertunda lama.
Gejalanya:
Satu request async sering melibatkan:
Kalau hanya melihat satu console.log, urutan sebenarnya sering tidak terlihat utuh.
Saat debugging async, baca urutannya dengan pola ini:
await yang memecah fungsi jadi dua bagian?Kalau masih bingung, tambahkan log di:
.then()awaitsetTimeout()Tujuannya bukan cuma mencari "mana yang error", tapi melihat kapan tiap bagian benar-benar masuk antrian eksekusi.
Promise saat butuh hasil asinkron yang bisa di-chain.async/await untuk membuat alur async lebih mudah dibaca.setTimeout(..., 0) sebagai pengganti urutan yang jelas.await..then(), .catch(), atau .finally().await selesai.