Halo kolega. Suatu ketika,
sebuah artikel tentang Habré ditulis oleh John Rezig tentang topik ini. 10 tahun telah berlalu, dan topiknya masih membutuhkan klarifikasi. Oleh karena itu, kami menawarkan kepada mereka yang tertarik untuk membaca artikel oleh Samer Buna, yang tidak hanya memberikan gambaran teoretis tentang timer dalam JavaScript (dalam konteks Node.js), tetapi juga tugas pada mereka.

Beberapa minggu yang lalu saya tweet pertanyaan berikut dari satu wawancara:
“Di mana kode sumber untuk fungsi setTimeout dan setInterval? Di mana Anda akan mencarinya? Anda tidak dapat Google itu :) "
*** Jawab sendiri, lalu baca ***
Sekitar setengah dari tanggapan terhadap tweet ini salah. Tidak, kasingnya TIDAK TERKAIT dengan V8 (atau VM lain) !!! Fungsi seperti
setTimeout
dan
setInterval
, dengan bangga disebut JavaScript JavaScript Timers, bukan bagian dari spesifikasi ECMAScript atau implementasi mesin JavaScript. Fungsi pengatur waktu diimplementasikan pada tingkat browser, sehingga implementasinya berbeda di berbagai browser. Pengatur waktu juga diterapkan secara asli di runtime Node.js. sendiri.
Di browser, fungsi timer utama merujuk ke antarmuka
Window
, yang juga terkait dengan beberapa fungsi dan objek lainnya. Antarmuka ini menyediakan akses global ke semua elemennya dalam cakupan utama JavaScript. Inilah sebabnya mengapa fungsi
setTimeout
dapat dieksekusi langsung di konsol browser.
Di Node, timer adalah bagian dari objek
global
, yang dirancang seperti antarmuka browser
Window
. Kode sumber untuk penghitung waktu di Node ditampilkan di
sini .
Tampaknya bagi seseorang bahwa ini hanyalah pertanyaan buruk dari wawancara - apa gunanya mengetahui hal ini ?! Saya, sebagai pengembang JavaScript, berpikir seperti ini: diasumsikan bahwa Anda harus mengetahui hal ini, karena sebaliknya mungkin menunjukkan bahwa Anda tidak begitu mengerti bagaimana V8 (dan mesin virtual lainnya) berinteraksi dengan browser dan Node.
Mari kita lihat beberapa contoh dan menyelesaikan beberapa tugas pengatur waktu, mari?
Anda dapat menggunakan perintah simpul untuk menjalankan contoh di artikel ini. Sebagian besar contoh yang dibahas di sini ditampilkan dalam kursus Getting Started with Node.js di Pluralsight.Eksekusi fungsi yang ditangguhkanPengatur waktu adalah fungsi tingkat tinggi yang dapat digunakan untuk menunda atau mengulangi pelaksanaan fungsi lainnya (pengatur waktu menerima fungsi seperti itu sebagai argumen pertama).
Berikut ini contoh eksekusi yang ditangguhkan:
Dalam contoh ini, menggunakan
setTimeout
pesan ucapan tertunda selama 4 detik. Argumen kedua untuk
setTimeout
adalah penundaan (dalam ms). Saya kalikan 4 dengan 1000 untuk mendapatkan 4 detik.
Argumen pertama untuk
setTimeout
adalah fungsi yang pelaksanaannya akan ditunda.
Jika Anda menjalankan file
example1.js
dengan perintah node, Node akan berhenti selama 4 detik dan kemudian menampilkan pesan selamat datang (diikuti oleh keluar).
Harap dicatat: argumen pertama ke
setTimeout
hanyalah
referensi fungsi . Seharusnya bukan fungsi
example1.js
- seperti
example1.js
. Berikut adalah contoh yang sama tanpa menggunakan fungsi bawaan:
const func = () => { console.log('Hello after 4 seconds'); }; setTimeout(func, 4 * 1000);
Melewati argumenJika fungsi yang digunakan
setTimeout
untuk menunda menerima argumen apa pun, maka Anda dapat menggunakan argumen yang tersisa dari fungsi
setTimeout
itu sendiri (setelah 2 yang telah kami pelajari) untuk mentransfer nilai argumen ke fungsi yang ditangguhkan.
Berikut ini sebuah contoh:
Fungsi
rocks
atas, tertunda selama 2 detik, mengambil argumen
who
, dan memanggil
setTimeout
memberinya nilai "Node.js" sebagai argumen
who
.
Saat menjalankan
example2.js
dengan perintah
node
, frasa “Node.js rocks” akan ditampilkan setelah 2 detik.
Pengatur Waktu Tugas # 1Jadi, berdasarkan materi yang sudah dipelajari tentang
setTimeout
, kami akan menampilkan 2 pesan berikut setelah penundaan yang sesuai.
- Pesan "Halo setelah 4 detik" ditampilkan setelah 4 detik.
- Pesan "Halo setelah 8 detik" ditampilkan setelah 8 detik.
BatasanDalam solusi Anda, Anda dapat menetapkan hanya satu fungsi yang berisi fungsi bawaan. Ini berarti bahwa banyak panggilan
setTimeout
harus menggunakan fungsi yang sama.
SolusiInilah cara saya memecahkan masalah ini:
Bagi saya,
theOneFunc
menerima argumen
delay
dan menggunakan nilai argumen
delay
ini dalam pesan yang ditampilkan di layar. Dengan demikian, fungsi dapat menampilkan pesan yang berbeda tergantung pada nilai keterlambatan apa yang akan kami informasikan.
Kemudian saya menggunakanOneFunc dalam dua panggilan
setTimeout
, dengan panggilan pertama dipecat setelah 4 detik dan yang kedua setelah 8 detik. Kedua panggilan
setTimeout
ini juga menerima argumen ke-3, mewakili argumen
delay
theOneFunc
.
Dengan mengeksekusi file
solution1.js
dengan perintah node, kami akan menampilkan persyaratan tugas, dan pesan pertama akan muncul setelah 4 detik, dan yang kedua setelah 8 detik.
Ulangi fungsi iniTetapi bagaimana jika saya meminta Anda untuk menampilkan pesan setiap 4 detik, untuk waktu yang tidak terbatas?
Tentu saja, Anda dapat
setTimeout
dalam satu lingkaran, tetapi penghitung waktu API juga menawarkan fungsi
setInterval
, yang dengannya Anda dapat memprogram eksekusi "abadi" dari operasi apa pun.
Berikut ini adalah contoh dari
setInterval
:
Kode ini akan menampilkan pesan setiap 3 detik. Jika Anda menjalankan
example3.js
dengan perintah
node
, Node akan menampilkan perintah ini hingga Anda memaksa proses untuk mengakhiri (CTRL + C).
Batalkan penghitung waktuKarena tindakan ditetapkan saat fungsi timer dipanggil, tindakan ini juga dapat dibatalkan sebelum dijalankan.
Panggilan
setTimeout
mengembalikan ID timer, dan Anda dapat menggunakan ID timer ini saat memanggil
clearTimeout
untuk membatalkan timer. Berikut ini sebuah contoh:
Timer sederhana ini akan menyala setelah 0 ms (yaitu, segera), tetapi ini tidak akan terjadi, karena kami menangkap nilai
timerId
dan segera membatalkan timer ini dengan memanggil
clearTimeout
.
Saat menjalankan
example4.js
dengan perintah
node
, Node tidak akan mencetak apa pun - prosesnya akan langsung berakhir.
Omong-omong, Node.js juga menyediakan cara lain untuk
setTimeout
dengan nilai 0 ms. Ada fungsi lain di Node.js timer API yang disebut
setImmediate
, dan pada dasarnya melakukan hal yang sama seperti
setTimeout
dengan nilai 0 ms, tetapi dalam hal ini Anda dapat menghilangkan penundaan:
setImmediate( () => console.log('I am equivalent to setTimeout with 0 ms'), );
Fungsi
setImmediate
didukung di semua browser . Jangan menggunakannya dalam kode klien.
Seiring dengan
clearTimeout
ada fungsi
clearInterval
yang melakukan hal yang sama, tetapi dengan panggilan
setInerval
, dan ada juga panggilan
clearImmediate
.
Timer Delay - sesuatu yang tidak dijaminPernahkah Anda memperhatikan bahwa dalam contoh sebelumnya, ketika melakukan operasi dengan
setTimeout
setelah 0 ms, operasi ini tidak terjadi segera (setelah
setTimeout
), tetapi hanya setelah semua kode skrip telah sepenuhnya dieksekusi (termasuk panggilan
clearTimeout
)?
Izinkan saya menjelaskan hal ini dengan sebuah contoh. Ini adalah panggilan
setTimeout
sederhana yang akan berfungsi dalam setengah detik - tetapi ini tidak terjadi:
Segera setelah mendefinisikan timer dalam contoh ini, kami secara sinkron memblokir lingkungan runtime dengan besar
for
loop. Nilai
1e10
adalah 1 dengan 10 nol, sehingga siklusnya berlangsung 10 miliar siklus prosesor (pada prinsipnya, ini mensimulasikan prosesor yang kelebihan beban). Node tidak dapat melakukan apa-apa sampai loop ini selesai.
Tentu saja, dalam praktiknya ini sangat buruk, tetapi contoh ini membantu untuk memahami bahwa penundaan
setTimeout
tidak dijamin, melainkan nilai
minimum . Nilai 500 ms berarti bahwa penundaan akan berlangsung setidaknya 500 ms. Bahkan, skrip akan membutuhkan waktu lebih lama untuk menampilkan garis sambutan di layar. Pertama, dia harus menunggu sampai siklus pemblokiran selesai.
Pengatur Waktu Masalah # 2Tulis skrip yang akan menampilkan pesan "Hello World" sekali per detik, tetapi hanya 5 kali. Setelah 5 iterasi, skrip akan menampilkan pesan "Selesai", setelah itu proses Node akan selesai.
Batasan : saat menyelesaikan masalah ini, Anda tidak dapat memanggil
setTimeout
.
Petunjuk : perlu counter.
SolusiInilah cara saya memecahkan masalah ini:
let counter = 0; const intervalId = setInterval(() => { console.log('Hello World'); counter += 1; if (counter === 5) { console.log('Done'); clearInterval(intervalId); } }, 1000);
Saya menetapkan 0 sebagai nilai awal
counter
, dan kemudian disebut
setInterval
, yang mengambil id-nya.
Fungsi yang ditangguhkan akan menampilkan pesan dan setiap kali menambah penghitung dengan satu. Di dalam fungsi yang ditangguhkan, kami memiliki pernyataan if, yang akan memeriksa apakah 5 iterasi telah berlalu. Setelah 5 iterasi, program menampilkan "Selesai" dan membersihkan nilai interval menggunakan konstanta
intervalId
ditangkap. Penundaan interval adalah 1000 ms.
Siapa yang sebenarnya memanggil fungsi yang ditangguhkan?Saat menggunakan JavaScript
this
di dalam fungsi biasa, seperti ini misalnya:
function whoCalledMe() { console.log('Caller is', this); }
nilai dalam
this
akan cocok dengan
pemanggil . Jika Anda mendefinisikan fungsi di atas di dalam Node REPL, maka objek
global
akan memanggilnya. Jika Anda mendefinisikan suatu fungsi di konsol browser, maka objek
window
akan memanggilnya.
Mari kita mendefinisikan fungsi sebagai properti dari objek untuk membuatnya lebih jelas:
const obj = { id: '42', whoCalledMe() { console.log('Caller is', this); } };
Sekarang, ketika kita akan langsung menggunakan tautan itu ketika bekerja dengan fungsi
obj.whoCallMe
, objek
obj
(diidentifikasi oleh
id
-nya) akan bertindak sebagai penelepon:

Sekarang pertanyaannya adalah: siapa yang akan menjadi penelepon jika Anda meneruskan tautan ke
obj.whoCallMe
ke
setTimetout
?
Siapa penelepon dalam kasus ini?Jawabannya akan berbeda tergantung di mana fungsi timer dijalankan. Dalam hal ini, ketergantungan pada siapa penelepon tidak dapat diterima. Anda akan kehilangan kendali atas penelepon, karena itu akan tergantung pada pelaksanaan timer yang dalam hal ini memanggil fungsi Anda. Jika Anda menguji kode ini dalam Node REPL, maka objek
Timeout
akan menjadi pemanggil:

Harap dicatat: ini penting hanya ketika JavaScript
this
digunakan di dalam fungsi biasa. Saat menggunakan fungsi panah, pemanggil seharusnya tidak mengganggu Anda sama sekali.
Pengatur Waktu Masalah # 3Tulis skrip yang akan terus menghasilkan pesan "Hello World" dengan berbagai penundaan. Mulailah dengan penundaan satu detik, dan kemudian tambahkan satu detik pada setiap iterasi. Pada iterasi kedua, penundaan akan menjadi 2 detik. Pada yang ketiga - tiga, dan seterusnya.
Sertakan penundaan dalam pesan yang ditampilkan. Anda harus mendapatkan sesuatu seperti ini:
Hello World. 1
Hello World. 2
Hello World. 3
...
Keterbatasan : variabel hanya dapat didefinisikan menggunakan const. Menggunakan let atau var tidak.
SolusiKarena durasi penundaan dalam tugas ini adalah variabel, Anda tidak dapat menggunakan
setInterval
sini, tetapi Anda dapat secara manual mengkonfigurasi eksekusi interval menggunakan
setTimeout
di dalam panggilan rekursif. Fungsi pertama yang dijalankan dengan
setTimeout
akan membuat penghitung waktu berikutnya, dan seterusnya.
Selain itu, karena Anda tidak dapat menggunakan
let
/
var
, kami tidak dapat memiliki penghitung untuk menambah penundaan untuk setiap panggilan rekursif; sebagai gantinya, Anda bisa menggunakan argumen fungsi rekursif untuk melakukan kenaikan selama panggilan rekursif.
Inilah cara mengatasi masalah ini:
const greeting = delay => setTimeout(() => { console.log('Hello World. ' + delay); greeting(delay + 1); }, delay * 1000); greeting(1);
Pengatur Waktu Tugas # 4Tulis skrip yang akan menampilkan pesan "Hello World" dengan struktur penundaan yang sama seperti pada tugas # 3, tetapi kali ini dalam kelompok yang terdiri dari 5 pesan, dan grup tersebut akan memiliki interval penundaan utama. Untuk grup pertama dari 5 pesan, kami memilih penundaan awal 100 ms, untuk yang berikutnya - 200 ms, untuk yang ketiga - 300 ms dan seterusnya.
Begini cara kerja skrip ini:
- Pada 100 ms, skrip menampilkan "Hello World" untuk pertama kalinya, dan melakukannya 5 kali dengan interval meningkat dalam 100 ms. Pesan pertama akan muncul setelah 100 ms, yang kedua setelah 200 ms, dll.
- Setelah 5 pesan pertama, skrip harus meningkatkan penundaan utama sebesar 200 ms. Dengan demikian, pesan ke-6 akan ditampilkan setelah 500 ms + 200 ms (700 ms), tanggal 7 - 900 ms, pesan ke-8 - setelah 1100 ms, dan seterusnya.
- Setelah 10 pesan, skrip harus meningkatkan interval penundaan utama hingga 300 ms. Pesan 11 harus ditampilkan setelah 500 ms + 1000 ms + 300 ms (18000 ms). Pesan ke-12 akan ditampilkan setelah 2100 ms, dll.
Menurut prinsip ini, program harus bekerja tanpa batas.
Sertakan penundaan dalam pesan yang ditampilkan. Anda harus mendapatkan sesuatu seperti ini (tidak ada komentar):
Hello World. 100 // 100
Hello World. 100 // 200
Hello World. 100 // 300
Hello World. 100 // 400
Hello World. 100 // 500
Hello World. 200 // 700
Hello World. 200 // 900
Hello World. 200 // 1100
...
Keterbatasan : Anda hanya dapat menggunakan panggilan untuk
setInterval
(dan tidak
setTimeout
) dan hanya SATU
if
.
SolusiKarena kita hanya dapat bekerja dengan panggilan
setInterval
, di sini kita perlu menggunakan rekursi dan juga meningkatkan penundaan panggilan
setInterval
berikutnya. Selain itu, kita memerlukan
if
untuk mewujudkan ini hanya setelah 5 panggilan ke fungsi rekursif ini.
Berikut ini adalah solusi yang mungkin:
let lastIntervalId, counter = 5; const greeting = delay => { if (counter === 5) { clearInterval(lastIntervalId); lastIntervalId = setInterval(() => { console.log('Hello World. ', delay); greeting(delay + 100); }, delay); counter = 0; } counter += 1; }; greeting(100);
Terima kasih untuk semua orang yang membacanya.