Kita mungkin memutuskan untuk menjalankan suatu fungsi bukan sekarang, tetapi pada waktu tertentu nanti. Itu disebut “menjadwalkan panggilan”.
Ada dua metode untuk itu:
setTimeout
memungkinkan kita menjalankan suatu fungsi satu kali setelah interval waktu tertentu.
setInterval
memungkinkan kita menjalankan suatu fungsi berulang kali, dimulai setelah interval waktu tertentu, lalu berulang terus menerus pada interval tersebut.
Metode ini bukan merupakan bagian dari spesifikasi JavaScript. Namun sebagian besar lingkungan memiliki penjadwal internal dan menyediakan metode ini. Secara khusus, mereka didukung di semua browser dan Node.js.
Sintaksnya:
biarkan timerId = setTimeout(func|kode, [penundaan], [arg1], [arg2], ...)
Parameter:
func|code
Fungsi atau serangkaian kode untuk dieksekusi. Biasanya, itu sebuah fungsi. Karena alasan historis, serangkaian kode dapat diteruskan, tapi itu tidak disarankan.
delay
Penundaan sebelum dijalankan, dalam milidetik (1000 ms = 1 detik), secara default 0.
arg1
, arg2
…
Argumen untuk fungsi tersebut
Misalnya, kode ini memanggil sayHi()
setelah satu detik:
fungsi ucapkan Hai() { peringatan('Halo'); } setTimeout(ucapkan Hai, 1000);
Dengan argumen:
fungsi sayHi(frasa, siapa) { waspada(frasa + ',' + siapa ); } setTimeout(sayHi, 1000, "Halo", "John"); // Halo, John
Jika argumen pertama adalah string, maka JavaScript akan membuat fungsi dari argumen tersebut.
Jadi, ini juga akan berhasil:
setTimeout("peringatan('Halo')", 1000);
Tetapi menggunakan string tidak disarankan, gunakan fungsi panah sebagai gantinya, seperti ini:
setTimeout(() => peringatan('Halo'), 1000);
Lewati suatu fungsi, tetapi jangan jalankan
Pengembang pemula terkadang membuat kesalahan dengan menambahkan tanda kurung ()
setelah fungsinya:
// salah! setTimeout(sayHi(), 1000);
Itu tidak berhasil, karena setTimeout
mengharapkan referensi ke suatu fungsi. Dan di sini sayHi()
menjalankan fungsinya, dan hasil eksekusinya diteruskan ke setTimeout
. Dalam kasus kita, hasil sayHi()
undefined
(fungsi tidak mengembalikan apa pun), jadi tidak ada yang dijadwalkan.
Panggilan ke setTimeout
mengembalikan “timer identifier” timerId
yang dapat kita gunakan untuk membatalkan eksekusi.
Sintaks untuk membatalkan:
biarkan timerId = setTimeout(...); clearTimeout(timerId);
Pada kode di bawah ini, kita menjadwalkan fungsi tersebut dan kemudian membatalkannya (berubah pikiran). Akibatnya, tidak terjadi apa-apa:
let timerId = setTimeout(() => alert("tidak pernah terjadi"), 1000); peringatan(timerId); // pengidentifikasi pengatur waktu clearTimeout(timerId); peringatan(timerId); // pengenal yang sama (tidak menjadi nol setelah dibatalkan)
Seperti yang bisa kita lihat dari keluaran alert
, di browser, pengidentifikasi pengatur waktu adalah angka. Di lingkungan lain, hal ini bisa menjadi sesuatu yang lain. Misalnya, Node.js mengembalikan objek pengatur waktu dengan metode tambahan.
Sekali lagi, tidak ada spesifikasi universal untuk metode ini, jadi tidak masalah.
Untuk browser, pengatur waktu dijelaskan di bagian pengatur waktu di HTML Living Standard.
Metode setInterval
memiliki sintaks yang sama dengan setTimeout
:
biarkan timerId = setInterval(func|kode, [penundaan], [arg1], [arg2], ...)
Semua argumen memiliki arti yang sama. Namun tidak seperti setTimeout
ia menjalankan fungsinya tidak hanya sekali, tetapi secara teratur setelah interval waktu tertentu.
Untuk menghentikan panggilan lebih lanjut, kita harus memanggil clearInterval(timerId)
.
Contoh berikut akan menampilkan pesan setiap 2 detik. Setelah 5 detik, output dihentikan:
// ulangi dengan interval 2 detik biarkan timerId = setInterval(() => alert('centang'), 2000); // setelah 5 detik berhenti setTimeout(() => { clearInterval(timerId); peringatan('berhenti'); }, 5000);
Waktu terus berjalan saat alert
ditampilkan
Di sebagian besar browser, termasuk Chrome dan Firefox, pengatur waktu internal terus “berdetak” sambil menampilkan alert/confirm/prompt
.
Jadi jika Anda menjalankan kode di atas dan tidak mengabaikan jendela alert
selama beberapa waktu, maka alert
berikutnya akan segera ditampilkan saat Anda melakukannya. Interval sebenarnya antar peringatan akan lebih pendek dari 2 detik.
Ada dua cara menjalankan sesuatu secara teratur.
Salah satunya adalah setInterval
. Yang lainnya adalah setTimeout
bersarang, seperti ini:
/** alih-alih: biarkan timerId = setInterval(() => alert('centang'), 2000); */ biarkan timerId = setTimeout(fungsi tick() { waspada('centang'); timerId = setTimeout(centang, 2000); // (*) }, 2000);
setTimeout
di atas menjadwalkan panggilan berikutnya tepat di akhir panggilan saat ini (*)
.
setTimeout
bersarang adalah metode yang lebih fleksibel daripada setInterval
. Dengan cara ini panggilan berikutnya mungkin dijadwalkan secara berbeda, bergantung pada hasil panggilan saat ini.
Misalnya, kita perlu menulis layanan yang mengirimkan permintaan ke server setiap 5 detik untuk meminta data, tetapi jika server kelebihan beban, intervalnya harus ditingkatkan menjadi 10, 20, 40 detik…
Berikut pseudocodenya:
biarkan tunda = 5000; biarkan timerId = setTimeout(fungsi permintaan() { ...kirim permintaan... if (permintaan gagal karena server kelebihan beban) { // menambah interval ke proses berikutnya penundaan *= 2; } timerId = setTimeout(permintaan, penundaan); }, menunda);
Dan jika fungsi yang kita jadwalkan membutuhkan CPU, maka kita dapat mengukur waktu yang diperlukan untuk eksekusi dan merencanakan panggilan berikutnya cepat atau lambat.
setTimeout
bersarang memungkinkan untuk mengatur penundaan antara eksekusi dengan lebih tepat daripada setInterval
.
Mari kita bandingkan dua fragmen kode. Yang pertama menggunakan setInterval
:
misalkan saya = 1; setInterval(fungsi() { fungsi(i++); }, 100);
Yang kedua menggunakan setTimeout
bersarang :
misalkan saya = 1; setTimeout(fungsi dijalankan() { fungsi(i++); setTimeout(jalankan, 100); }, 100);
Untuk setInterval
penjadwal internal akan menjalankan func(i++)
setiap 100 md:
Apakah Anda memperhatikan?
Penundaan sebenarnya antara pemanggilan func
untuk setInterval
lebih kecil dari pada kode!
Itu normal, karena waktu yang dibutuhkan oleh eksekusi func
“menghabiskan” sebagian dari interval.
Ada kemungkinan bahwa eksekusi func
ternyata lebih lama dari yang kami harapkan dan membutuhkan waktu lebih dari 100ms.
Dalam hal ini mesin menunggu func
selesai, kemudian memeriksa penjadwal dan jika waktunya habis, segera jalankan kembali.
Dalam kasus edge, jika fungsi selalu dijalankan lebih lama dari delay
ms, maka panggilan akan terjadi tanpa jeda sama sekali.
Dan inilah gambar untuk setTimeout
yang disarangkan :
setTimeout
bersarang menjamin penundaan tetap (di sini 100 md).
Itu karena panggilan baru direncanakan pada akhir panggilan sebelumnya.
Pengumpulan sampah dan panggilan balik setInterval/setTimeout
Ketika suatu fungsi diteruskan dalam setInterval/setTimeout
, referensi internal dibuat untuk fungsi tersebut dan disimpan dalam penjadwal. Ini mencegah fungsi tersebut dikumpulkan dari sampah, meskipun tidak ada referensi lain ke dalamnya.
// fungsi tersebut tetap berada di memori hingga penjadwal memanggilnya setTimeout(fungsi() {...}, 100);
Untuk setInterval
fungsinya tetap berada di memori hingga clearInterval
dipanggil.
Ada efek sampingnya. Suatu fungsi mereferensikan lingkungan leksikal luar, jadi, selama fungsi tersebut aktif, variabel luar juga tetap aktif. Mereka mungkin memerlukan lebih banyak memori daripada fungsi itu sendiri. Jadi ketika fungsi terjadwal sudah tidak kita perlukan lagi, lebih baik batalkan saja, walaupun kecil sekali.
Ada kasus penggunaan khusus: setTimeout(func, 0)
, atau hanya setTimeout(func)
.
Ini menjadwalkan pelaksanaan func
sesegera mungkin. Namun penjadwal akan memanggilnya hanya setelah skrip yang sedang dijalankan selesai.
Jadi fungsinya dijadwalkan untuk dijalankan “tepat setelah” skrip saat ini.
Misalnya, ini menghasilkan “Halo”, lalu langsung “Dunia”:
setTimeout(() => alert("Dunia")); peringatan("Halo");
Baris pertama “memasukkan panggilan ke kalender setelah 0 ms”. Namun penjadwal hanya akan “memeriksa kalender” setelah skrip saat ini selesai, jadi "Hello"
adalah yang pertama, dan "World"
– setelahnya.
Ada juga kasus penggunaan batas waktu tanpa penundaan terkait browser tingkat lanjut, yang akan kita bahas di bab Perulangan peristiwa: tugas mikro dan tugas makro.
Nol penundaan sebenarnya bukan nol (di browser)
Di browser, ada batasan seberapa sering penghitung waktu bersarang dapat dijalankan. Standar Hidup HTML mengatakan: "setelah lima penghitung waktu bersarang, intervalnya dipaksa menjadi setidaknya 4 milidetik.".
Mari kita tunjukkan maksudnya dengan contoh di bawah ini. Panggilan setTimeout
di dalamnya menjadwalkan ulang dirinya sendiri tanpa penundaan. Setiap panggilan mengingat waktu sebenarnya dari panggilan sebelumnya dalam larik times
. Seperti apa penundaan sebenarnya? Mari kita lihat:
biarkan mulai = Tanggal.sekarang(); biarkan kali = []; setTimeout(fungsi dijalankan() { times.push(Tanggal.sekarang() - mulai); // ingat penundaan dari panggilan sebelumnya if (mulai + 100 < Tanggal.sekarang()) peringatan(waktu); // tampilkan penundaan setelah 100 ms lain setTimeout(jalankan); // jika tidak, jadwalkan ulang }); // contoh keluarannya: // 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
Pengatur waktu pertama segera dijalankan (seperti yang tertulis di spesifikasi), dan kemudian kita melihat 9, 15, 20, 24...
. Penundaan wajib 4+ ms antar pemanggilan mulai berlaku.
Hal serupa terjadi jika kita menggunakan setInterval
alih-alih setTimeout
: setInterval(f)
menjalankan f
beberapa kali dengan penundaan nol, dan kemudian dengan penundaan 4+ ms.
Batasan itu berasal dari zaman kuno dan banyak naskah yang mengandalkannya, sehingga ada karena alasan sejarah.
Untuk JavaScript sisi server, batasan tersebut tidak ada, dan terdapat cara lain untuk menjadwalkan pekerjaan asinkron langsung, seperti setImmediate untuk Node.js. Jadi catatan ini khusus untuk browser.
Metode setTimeout(func, delay, ...args)
dan setInterval(func, delay, ...args)
memungkinkan kita menjalankan func
sekali/secara teratur setelah delay
milidetik.
Untuk membatalkan eksekusi, kita harus memanggil clearTimeout/clearInterval
dengan nilai yang dikembalikan oleh setTimeout/setInterval
.
Panggilan setTimeout
yang disarangkan adalah alternatif yang lebih fleksibel dibandingkan setInterval
, memungkinkan kita menyetel waktu antar eksekusi dengan lebih tepat.
Penjadwalan nol penundaan dengan setTimeout(func, 0)
(sama seperti setTimeout(func)
) digunakan untuk menjadwalkan panggilan “sesegera mungkin, tetapi setelah skrip saat ini selesai”.
Browser membatasi penundaan minimal untuk lima atau lebih panggilan bertumpuk setTimeout
atau setInterval
(setelah panggilan ke-5) hingga 4 md. Itu karena alasan historis.
Harap dicatat bahwa semua metode penjadwalan tidak menjamin penundaan yang tepat.
Misalnya, pengatur waktu dalam browser mungkin melambat karena berbagai alasan:
CPU kelebihan beban.
Tab browser berada dalam mode latar belakang.
Laptop dalam mode hemat baterai.
Semua itu dapat meningkatkan resolusi pengatur waktu minimal (penundaan minimal) hingga 300 ms atau bahkan 1000 ms tergantung pada browser dan pengaturan kinerja tingkat OS.
pentingnya: 5
Tulis fungsi printNumbers(from, to)
yang mengeluarkan angka setiap detik, dimulai dari from
dan diakhiri dengan to
.
Buatlah dua varian penyelesaiannya.
Menggunakan setInterval
.
Menggunakan setTimeout
bersarang.
Menggunakan setInterval
:
fungsi printNumbers(dari, ke) { biarkan arus = dari; biarkan timerId = setInterval(fungsi() { peringatan(saat ini); jika (saat ini == ke) { clearInterval(timerId); } saat ini++; }, 1000); } // penggunaan: printNumbers(5, 10);
Menggunakan setTimeout
bersarang :
fungsi printNumbers(dari, ke) { biarkan arus = dari; setTimeout(fungsi pergi() { peringatan(saat ini); jika (saat ini < ke) { setTimeout(pergi, 1000); } saat ini++; }, 1000); } // penggunaan: printNumbers(5, 10);
Perhatikan bahwa dalam kedua solusi, ada penundaan awal sebelum keluaran pertama. Fungsi ini dipanggil setelah 1000ms
untuk pertama kalinya.
Jika kita juga ingin fungsi tersebut segera berjalan, maka kita dapat menambahkan panggilan tambahan pada baris terpisah, seperti ini:
fungsi printNumbers(dari, ke) { biarkan arus = dari; fungsi pergi() { peringatan(saat ini); jika (saat ini == ke) { clearInterval(timerId); } saat ini++; } pergi(); biarkan timerId = setInterval(pergi, 1000); } printNumbers(5, 10);
pentingnya: 5
Pada kode di bawah ini ada panggilan setTimeout
yang dijadwalkan, lalu penghitungan berat dijalankan, yang memerlukan waktu lebih dari 100 md untuk menyelesaikannya.
Kapan fungsi terjadwal akan berjalan?
Setelah putaran.
Sebelum putaran.
Di awal perulangan.
alert
apa yang akan ditampilkan?
misalkan saya = 0; setTimeout(() => peringatan(i), 100); // ? // asumsikan waktu untuk menjalankan fungsi ini >100 ms untuk(misalkan j = 0; j < 100000000; j++) { saya++; }
setTimeout
apa pun akan berjalan hanya setelah kode saat ini selesai.
i
akan menjadi yang terakhir: 100000000
.
misalkan saya = 0; setTimeout(() => peringatan(i), 100); // 100000000 // asumsikan waktu untuk menjalankan fungsi ini >100 ms untuk(misalkan j = 0; j < 100000000; j++) { saya++; }