Pengatur waktu JavaScript: semua yang perlu Anda ketahui

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 ditangguhkan

Pengatur 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:

 // example1.js setTimeout( () => { console.log('Hello after 4 seconds'); }, 4 * 1000 ); 

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 argumen

Jika 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.

 // : func(arg1, arg2, arg3, ...) //  : setTimeout(func, delay, arg1, arg2, arg3, ...) 

Berikut ini sebuah contoh:

 // example2.js const rocks = who => { console.log(who + ' rocks'); }; setTimeout(rocks, 2 * 1000, 'Node.js'); 

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 # 1

Jadi, 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.

Batasan

Dalam solusi Anda, Anda dapat menetapkan hanya satu fungsi yang berisi fungsi bawaan. Ini berarti bahwa banyak panggilan setTimeout harus menggunakan fungsi yang sama.

Solusi

Inilah cara saya memecahkan masalah ini:

 // solution1.js const theOneFunc = delay => { console.log('Hello after ' + delay + ' seconds'); }; setTimeout(theOneFunc, 4 * 1000, 4); setTimeout(theOneFunc, 8 * 1000, 8); 

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 ini

Tetapi 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 :

 // example3.js setInterval( () => console.log('Hello every 3 seconds'), 3000 ); 

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 waktu

Karena 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:

 // example4.js const timerId = setTimeout( () => console.log('You will not see this one!'), 0 ); clearTimeout(timerId); 

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 dijamin

Pernahkah 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:

 // example5.js setTimeout( () => console.log('Hello after 0.5 seconds. MAYBE!'), 500, ); for (let i = 0; i < 1e10; i++) { //    } 

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 # 2

Tulis 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.

Solusi

Inilah 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); } }; //     : obj.whoCallMe 

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 ?

 //       ?? setTimeout(obj.whoCalledMe, 0); 

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 # 3

Tulis 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.

Solusi

Karena 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 # 4

Tulis 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 .

Solusi

Karena 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.

Source: https://habr.com/ru/post/id426709/


All Articles