Hampir setiap aplikasi Node.js memakai penjadwalan berbasis waktu: membersihkan cache secara berkala, membatasi percobaan ulang, atau sekadar menunda pesan. Dua API utamanya adalah setTimeout untuk eksekusi sekali dan setInterval untuk eksekusi berulang, lengkap dengan pasangan pembersihnya clearTimeout dan clearInterval. Chapter ini membahas keduanya beserta detail yang sering terlewat: object Timeout yang dikembalikan, method unref() yang membebaskan proses dari timer, serta batas presisi waktu tunda yang wajib kita pahami agar tidak salah mendesain.
setTimeout dan clearTimeout #
setTimeout(callback, delay, ...args)
menjadwalkan callback berjalan sekali setelah delay milidetik. Fungsi ini mengembalikan object Timeout
— bukan sekadar angka ID seperti di browser — yang bisa kita berikan ke clearTimeout() untuk membatalkan penjadwalan sebelum callbacknya berjalan. Argumen setelah delay akan diteruskan ke callback:
const tundaDibatalkan = setTimeout(() => {
console.log("B tidak pernah tercetak");
}, 20);
clearTimeout(tundaDibatalkan);
setTimeout((nama, status) => {
console.log(`Halo ${nama}, status ${status}`);
}, 10, "Budi", "aktif");
setTimeout(() => {
console.log("C tercetak paling akhir (tunda 30 ms)");
}, 30);
console.log("Kode sinkron selesai lebih dulu");Output yang Dihasilkan:
Kode sinkron selesai lebih dulu
Halo Budi, status aktif
C tercetak paling akhir (tunda 30 ms)Mari kita telaah perilaku program di atas:
- Baris 1-4: timer B dijadwalkan berjalan setelah 20 milidetik, tetapi
clearTimeoutmembatalkannya sebelum sempat berjalan — pesannya tidak pernah muncul. - Baris 6-8: argumen
"Budi"dan"aktif"setelahdelayditeruskan langsung ke parameternamadanstatuscallback — tanpa perlu closure. - Baris 10-12: timer C dengan jeda 30 milidetik. Eksekusi tetap mengikuti fase timers event loop, bukan jam presisi.
- Baris 14: kode sinkron selalu tuntas lebih dulu — memasang timer hanya mendaftarkan pekerjaan, tidak menjalankannya.
setInterval dan clearInterval #
setInterval(callback, delay)
menjadwalkan callback berjalan berulang kali setiap delay milidetik hingga dibatalkan. Karena sifatnya berulang, satu-satunya cara menghentikannya adalah clearInterval():
let detak = 0;
const pemantau = setInterval(() => {
detak++;
console.log(`detak ke-${detak}`);
if (detak === 3) {
clearInterval(pemantau);
console.log("interval dihentikan, proses bisa keluar");
}
}, 10);Output yang Dihasilkan:
detak ke-1
detak ke-2
detak ke-3
interval dihentikan, proses bisa keluarProgram mencatat tiga detak dengan jeda 10 milidetik, lalu memanggil clearInterval dari dalam callback ketika penghitung mencapai tiga. Setelah interval dibersihkan, tidak ada lagi pekerjaan tertunda sehingga event loop berhenti dan proses keluar dengan rapi.
unref: Timer yang Tidak Menahan Proses #
Event loop tetap hidup selama ada handle aktif yang dihitung (referenced), dan timer termasuk yang dihitung. Method unref()
pada object Timeout mengubah status itu: timer tetap berjalan bila proses memang masih hidup karena pekerjaan lain, tetapi timer sendiri tidak lagi dihitung sebagai alasan untuk tetap hidup:
const timerPanjang = setTimeout(() => {
console.log("lima detik kemudian (tidak pernah tercetak)");
}, 5000);
timerPanjang.unref();
console.log("timer 5 detik tidak menahan proses tetap hidup");Output yang Dihasilkan:
timer 5 detik tidak menahan proses tetap hidupSatu baris tercetak lalu proses langsung keluar — tanpa menunggu lima detik. Bagaimana bila proses memang tetap hidup karena timer lain? Timer unref tetap dijalankan pada waktunya:
const timerLemah = setTimeout(() => {
console.log("timer unref tetap berjalan (tunda 30 ms)");
}, 30);
timerLemah.unref();
const timerKuat = setTimeout(() => {
console.log("timer biasa menahan proses (tunda 60 ms)");
}, 60);Output yang Dihasilkan:
timer unref tetap berjalan (tunda 30 ms)
timer biasa menahan proses (tunda 60 ms)Kedua pesan tercetak berurutan: timer 60 milidetik yang masih referenced menahan proses tetap hidup, dan selama proses hidup, timer unref berjalan normal pada detik ke-30 milidetik. Pola ini lazim untuk pekerjaan latar belakang seperti heartbeat atau pembaruan metrik: berjalan bila aplikasi memang sibuk, tetapi tidak menunda proses keluar saat aplikasi selesai. Method ref() mengembalikan status timer menjadi dihitung kembali.
Presisi Waktu Tunda #
Timer Node.js menjanjikan batas bawah, bukan ketepatan. Tiga aturan pentingnya:
- Jeda minimum adalah 1 milidetik. Nilai
delayyang kurang dari 1 — termasuk 0 dan angka negatif — maupun lebih besar dari 2.147.483.647 dibulatkan menjadi 1 milidetik. Karena itusetTimeout(fn, 0)secara efektif sama dengansetTimeout(fn, 1). - Callback tidak pernah berjalan lebih awal, tetapi boleh terlambat. Fase timers mengeksekusi callback yang batas waktunya sudah terlampaui; callback yang belum jatuh waktunya ditunda ke putaran berikutnya.
- Kode sinkron yang memakan lama ikut menunda timer. Selama call stack belum kosong, event loop tidak bisa berputar — jeda 10 milidetik bisa menjadi jauh lebih lama bila ada perhitungan berat di antaranya.
Untuk gaya kode modern, module timers/promises menyediakan versi Promise dari setTimeout sehingga penundaan bisa di-await langsung — dokumentasinya ada di halaman timers/promises
.
Penjadwalan menyelesaikan masalah “kapan”, tetapi tidak menyentuh masalah “berapa lama kode berjalan”. Perhitungan CPU berat tetap memblokir event loop berapa pun timer yang kita pasang — jalan keluarnya adalah thread terpisah, yang dibedah pada Chapter Worker Threads.