Timer

setTimeout dan setInterval serta alasan JavaScript tidak pernah tidur.

Daftar isi

Program yang hanya berisi kode sinkron tidak punya cara menunggu, padahal banyak pekerjaan baru bermakna setelah jeda, misalnya mengulang percobaan koneksi setelah beberapa detik atau memperbarui jam di layar setiap menit. Topik ini membahas dua fungsi penjadwal bawaan, yaitu setTimeout untuk menunda dan setInterval untuk mengulang, beserta pasangan pembatalnya. Setelah membacanya, eksekusi dapat ditunda, diulang, dan dihentikan dengan rapi. Topik ini juga menjelaskan mengapa penundaan tidak pernah persis dan mengapa JavaScript tidak pernah tidur di tengah jalan.

setTimeout menerima dua argumen utama, yaitu callback yang ingin dijalankan dan penundaannya dalam milidetik. Fungsinya segera kembali setelah memasang jadwal, program di bawahnya terus berjalan, dan callback-nya baru dieksekusi setelah waktunya tiba. Inilah alasan JavaScript tidak pernah tidur. Menunggu 40 milidetik bukan berarti berhenti selama 40 milidetik, melainkan hanya mendaftarkan callback untuk dieksekusi nanti.

js tunda.js
// setTimeout menjadwalkan callback tanpa menghentikan program.
console.log("pesan dijadwalkan 40 ms lagi");
setTimeout(() => {
  console.log("callback berjalan setelah menunggu 40 ms");
}, 40);
console.log("program tetap berjalan sampai baris terakhir");
text Keluaran
pesan dijadwalkan 40 ms lagi
program tetap berjalan sampai baris terakhir
callback berjalan setelah menunggu 40 ms

Dua baris sinkron tercetak lebih dahulu, sedangkan callback-nya menyusul 40 milidetik kemudian. Pada program Node, prosesnya pun ikut menunggu sampai semua timer selesai, sehingga contoh di atas berakhir dengan sendirinya setelah callback-nya selesai tercetak. Pola pemasangan jadwal yang langsung kembali ini berlaku sama di browser, dan callback-nya selalu masuk antrean yang diatur event loop.

Argumen penundaannya boleh dikosongkan. Tanpa argumen itu, jeda diperlakukan nol, artinya callback tetap menunggu giliran event loop tanpa menambah waktu tunggu. Angka yang dioper boleh berupa milidetik apa pun, dari 500 untuk setengah detik sampai 60000 untuk satu menit. Untuk jeda panjang, perhitungannya sebaiknya ditulis dari bentuk yang mudah dibaca, misalnya 5 * 60 * 1000 untuk lima menit, supaya maksudnya langsung terlihat.

Membatalkan jadwal dengan clearTimeout#

Jadwal yang sudah dipasang tidak selalu harus dijalankan. setTimeout mengembalikan penanda jadwal yang disimpan dalam satu variabel, dan clearTimeout menerima penanda itu untuk membatalkan callback yang belum sempat berjalan. Pembatalan seperti ini berguna ketika kondisi berubah lebih cepat dari jadwalnya, misalnya percobaan ulang yang dibatalkan karena koneksi sudah pulih.

js batalkan.js
// clearTimeout membatalkan callback yang belum sempat berjalan.
const id = setTimeout(() => {
  console.log("callback ini tidak akan pernah tampil");
}, 50);

clearTimeout(id);
console.log("callback sudah dibatalkan sebelum waktunya");
text Keluaran
callback sudah dibatalkan sebelum waktunya

Keluarannya hanya satu baris karena clearTimeout dipanggil sebelum 50 milidetik berlalu, sehingga callback yang sudah dijadwalkan tidak pernah dieksekusi. Pembatalan yang dilakukan setelah callback berjalan tidak berakibat apa pun, dan clearTimeout hanya diam tanpa error. Karena itu, pola yang aman adalah selalu menyimpan penanda jadwal dalam satu variabel, lalu membatalkannya pada titik yang jelas.

Pola pembatalan ini juga menjadi fondasi pola yang lebih besar, misalnya kolom pencarian yang menunggu pengguna selesai mengetik. Setiap ketikan baru membatalkan jadwal ketikan sebelumnya, lalu memasang jadwal baru, dan pencarian hanya dieksekusi ketika ada jeda ketikan yang cukup panjang. Mekanismenya persis contoh di atas, satu variabel penanda dan satu pemanggilan clearTimeout, hanya saja dipasang pada event ketikan.

Mengulang dengan setInterval dan clearInterval#

setInterval memakai bentuk argumen yang sama dengan setTimeout, tetapi callback-nya dijadwalkan berulang pada setiap jeda sampai dihentikan. Penghentinya adalah clearInterval, yang menerima penanda yang dikembalikan saat pemasangan. Pola yang lazim adalah menghitung berapa kali callback sudah berjalan, lalu menghentikan sendiri intervalnya ketika target tercapai.

js interval.js
// setInterval berjalan berulang sampai dihentikan clearInterval.
let detak = 0;

const id = setInterval(() => {
  detak = detak + 1;
  console.log("detak ke-" + detak);
  if (detak === 3) {
    clearInterval(id);
    console.log("interval dihentikan");
  }
}, 20);
text Keluaran
detak ke-1
detak ke-2
detak ke-3
interval dihentikan

Callback berjalan tiga kali dengan jeda 20 milidetik, lalu clearInterval menghentikan jadwalnya dari dalam callback sendiri. Program Node berakhir segera setelah baris terakhir karena tidak ada timer yang tersisa. Peragaan berikut mempraktikkan pola yang sama di browser, tekan Mulai untuk memulai detak dan Berhenti untuk menghentikannya.

Kode sumber demo
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <title>Demo interval dengan mulai dan berhenti</title>
  <style>
    body { font-family: "Segoe UI", Arial, sans-serif; margin: 14px; font-size: 14px; color: #1f2937; }
    button { padding: 6px 12px; margin-right: 8px; margin-bottom: 10px; cursor: pointer; }
    p { font-size: 18px; margin: 6px 0 12px; }
  </style>
</head>
<body>
  <p id="detak">Detak: 0</p>
  <button id="tombol-mulai" type="button">Mulai</button>
  <button id="tombol-berhenti" type="button">Berhenti</button>
  <script src="index.js"></script>

<script>
// Demo setInterval dengan tombol mulai dan berhenti.

const tampilan = document.getElementById("detak");
let id = null;
let detak = 0;

function perbarui() {
  tampilan.textContent = "Detak: " + detak;
}

document.getElementById("tombol-mulai").addEventListener("click", () => {
  if (id !== null) {
    return;
  }
  detak = 0;
  perbarui();
  id = setInterval(() => {
    detak = detak + 1;
    perbarui();
  }, 500);
});

document.getElementById("tombol-berhenti").addEventListener("click", () => {
  clearInterval(id);
  id = null;
});

</script>
<script>
// Demo setInterval dengan tombol mulai dan berhenti.

const tampilan = document.getElementById("detak");
let id = null;
let detak = 0;

function perbarui() {
  tampilan.textContent = "Detak: " + detak;
}

document.getElementById("tombol-mulai").addEventListener("click", () => {
  if (id !== null) {
    return;
  }
  detak = 0;
  perbarui();
  id = setInterval(() => {
    detak = detak + 1;
    perbarui();
  }, 500);
});

document.getElementById("tombol-berhenti").addEventListener("click", () => {
  clearInterval(id);
  id = null;
});

</script></body>
</html>

Penundaan minimum, bukan penundaan pasti#

Angka yang dioper ke setTimeout dan setInterval sebaiknya dibaca sebagai penundaan minimum, bukan janji tepat waktu. Callback baru dieksekusi setelah dua syarat terpenuhi, yaitu waktunya sudah tiba dan call stack sudah kosong. Bila ada kode sinkron yang masih berjalan, callback yang sudah jatuh tempo pun antre seperti terlihat pada topik Event loop. Karena itu, jeda 20 milidetik pada praktiknya bisa menjadi 25 milidetik atau lebih, tergantung kesibukan program.

Lingkungan eksekusinya turut menegakkan batas bawah tambahan. Browser modern menegakkan penundaan minimum sekitar 4 milidetik untuk timer yang bersarang dalam, yakni timer yang dipasang dari dalam callback timer sebelumnya setelah beberapa tingkat. Node.js sendiri memperlakukan jeda 0 sebagai sekitar 1 milidetik. Nilai-nilai ini jarang berpengaruh pada program biasa, tetapi penting diingat ketika mengukur sesuatu yang butuh ketepatan tinggi. Untuk kebutuhan waktu yang benar-benar presisi, timer bukanlah alat yang tepat.

Hal serupa berlaku pada setInterval. Bila satu callback memakan waktu lebih lama dari jeda yang ditetapkan, callback berikutnya tidak berjalan serentak dengan pendahulunya. Eksekusinya justru menyusul begitu call stack kosong, karena keduanya tetap dilayani satu thread yang sama. Interval yang rapat sebaiknya diberi jeda yang jauh lebih panjang daripada durasi callbacknya. Alternatif lain adalah pola setTimeout berantai, yang memasang jadwal berikutnya setelah pekerjaan selesai.

Ringkasan#

  • setTimeout menunda satu callback dan setInterval mengulangnya, keduanya segera kembali tanpa menghentikan program.
  • clearTimeout dan clearInterval membatalkan jadwal memakai penanda yang dikembalikan saat pemasangan.
  • Penundaan adalah nilai minimum, callback baru berjalan setelah waktunya tiba dan call stack kosong.
  • Interval yang tidak dihentikan berjalan selama program hidup, sehingga setiap interval perlu kondisi berhenti yang jelas.

Referensi#