Javascript Event Loop Async Await Promise
Definisi
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.
Kenapa penting
Banyak bug async bukan berasal dari Promise yang salah, tapi dari salah paham urutan eksekusi. Contohnya:
- callback terasa "lebih lambat" dari kode synchronous
console.log()tampil dalam urutan yang tidak diduga- state UI berubah duluan sebelum data selesai
- error
try/catchtidak menangkap Promise rejection karena konteksnya salah
Kalau urutan call stack, task queue, dan microtask queue tidak dipahami, debugging async biasanya jadi tebak-tebakan.
Komponen Utama
1) Call stack
Call stack adalah tempat JavaScript mengeksekusi fungsi yang sedang berjalan.
- Kode synchronous masuk dan keluar dari stack secara langsung.
- Selama stack masih penuh, event loop tidak akan memproses task lain.
- Kode yang terlalu berat di stack bisa membuat UI terasa freeze.
2) Task queue
Task queue menyimpan task makro seperti:
- event DOM seperti click atau input
- timer seperti
setTimeout()dansetInterval() - beberapa callback I/O atau message event
Task di queue ini tidak masuk ke stack sebelum stack kosong dan microtask queue sudah dikuras.
3) Microtask queue
Microtask queue menyimpan pekerjaan yang diprioritaskan lebih tinggi daripada task biasa.
Contoh isi microtask queue:
- callback dari
Promise.then() - callback dari
Promise.catch() - callback dari
Promise.finally() - continuation dari
await queueMicrotask()
Aturannya penting: setelah satu task selesai, JavaScript menguras semua microtask yang ada sebelum lanjut ke task berikutnya.
Urutan Eksekusi Referensial
Urutan sederhananya begini:
- JavaScript menjalankan kode synchronous di call stack.
- Saat bertemu operasi asinkron, browser/Node mendaftarkan pekerjaan latar belakangnya.
- Jika operasi selesai, callback-nya masuk ke task queue atau microtask queue, tergantung jenis API.
- Event loop hanya memindahkan callback ke call stack ketika stack kosong.
- Setelah satu task selesai, microtask queue dijalankan sampai habis.
- Baru setelah itu event loop mengambil task berikutnya.
Intinya:
- synchronous code selalu didahulukan
- microtask menang atas task queue
- Promise callback biasanya jalan sebelum
setTimeout(..., 0)
Diagram Alur Eksekusi
Kapan Promise Callback Dijadwalkan
Callback dari Promise tidak dijalankan saat Promise dibuat. Callback dijadwalkan ketika Promise berubah status dan ada handler yang terpasang.
Contoh penting
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.- Setelah stack kosong, microtask queue dijalankan, jadi
2muncul terakhir.
Promise yang sudah resolved tetap async
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/await dalam Alur Ini
async membuat fungsi selalu mengembalikan Promise.
await memecah eksekusi fungsi async menjadi dua fase:
- fase 1: berjalan sampai titik
await - fase 2: lanjut lagi setelah Promise selesai, lewat microtask queue
Contoh
async function run() {
console.log('A');
await Promise.resolve();
console.log('B');
}
console.log('C');
run();
console.log('D');Urutan output:
C
A
D
BKenapa begitu
console.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.- Setelah stack kosong,
console.log('B')jalan dari microtask queue.
Perbandingan Task Queue vs 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 |
Contoh urutan campuran
console.log('start');
setTimeout(() => {
console.log('timeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise');
});
console.log('end');Output:
start
end
promise
timeoutJebakan Umum Saat Debugging Async
1) Mengira setTimeout(..., 0) jalan paling cepat
setTimeout(..., 0) bukan berarti langsung saat itu juga. Callback tetap menunggu giliran di task queue.
Kalau ada microtask di depan, microtask akan selesai dulu.
2) Mengira then() berjalan sinkron
then() selalu menjadwalkan callback. Ia tidak dieksekusi langsung pada baris yang sama.
Ini sering bikin log kelihatan "loncat".
3) try/catch tidak menangkap rejection di luar konteks await
try {
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');
}4) Melupakan bahwa await hanya menunggu Promise di depannya
await tidak memblokir event loop. Ia hanya menunda kelanjutan fungsi async itu sendiri.
Artinya:
- kode di luar fungsi async tetap lanjut
- tugas lain tetap bisa diproses
- UI tidak otomatis freeze hanya karena ada
await
5) Microtask chain yang terlalu panjang
Kalau microtask terus menambahkan microtask baru, task queue bisa tertunda lama.
Gejalanya:
- timer terasa lambat
- event input terasa terlambat diproses
- debugging terasa aneh karena log Promise terus mendominasi
6) Salah menebak urutan karena log berasal dari banyak lapisan
Satu request async sering melibatkan:
- synchronous setup
- Promise resolution
- microtask continuation
- timer fallback
- render update
Kalau hanya melihat satu console.log, urutan sebenarnya sering tidak terlihat utuh.
Cara Membaca Urutan Debugging
Saat debugging async, baca urutannya dengan pola ini:
- Mana kode synchronous yang pasti jalan dulu?
- Callback ini berasal dari Promise atau timer?
- Apakah callback itu microtask atau task?
- Apakah ada
awaityang memecah fungsi jadi dua bagian? - Apakah ada chain microtask yang panjang?
Kalau masih bingung, tambahkan log di:
- sebelum Promise dibuat
- di dalam
.then() - tepat setelah
await - di dalam
setTimeout()
Tujuannya bukan cuma mencari "mana yang error", tapi melihat kapan tiap bagian benar-benar masuk antrian eksekusi.
Catatan Implementasi
- Gunakan
Promisesaat butuh hasil asinkron yang bisa di-chain. - Gunakan
async/awaituntuk membuat alur async lebih mudah dibaca. - Jangan mengandalkan
setTimeout(..., 0)sebagai pengganti urutan yang jelas. - Ingat bahwa semua handler Promise adalah microtask, jadi prioritasnya lebih tinggi daripada timer.
- Saat debugging, bedakan antara kapan Promise dibuat, kapan resolve terjadi, dan kapan callback dijalankan.
- Kalau urutan terasa aneh, cek dulu apakah yang kamu lihat itu synchronous log, microtask, atau task queue.
Istilah
- Call stack: tumpukan eksekusi fungsi synchronous.
- Task queue: antrian callback makro seperti timer dan event.
- Microtask queue: antrian prioritas tinggi untuk Promise dan continuation
await. - Promise callback: callback di
.then(),.catch(), atau.finally(). - Continuation: sisa eksekusi fungsi async setelah
awaitselesai. - Event loop: mekanisme yang memindahkan pekerjaan dari queue ke call stack saat stack kosong.