(Untuk kesan penuh, baca dengan suara serak sambil dikelilingi oleh awan asap)

Semuanya bermula pada hari musim gugur kelabu. Langit mendung, angin bertiup, dan seseorang memberitahu saya bahawa setTimeout(0)
rata-rata menimbulkan kelewatan 4 ms. Mereka mendakwa itulah waktu yang diperlukan untuk melepaskan callback dari stack, ke antrian callback dan kembali ke stack lagi. Saya fikir ia terdengar mencurigakan (ini adalah sedikit yang anda bayangkan saya berwarna hitam dan putih dengan cerut di mulut saya). Memandangkan saluran paip rendering perlu dijalankan setiap 16 ms untuk membolehkan animasi lancar, 4 ms sepertinya lama untuk saya. Masa yang sangat lama.
Beberapa ujian naif di devtools dengan console.time()
mengesahkannya. Kelewatan purata sepanjang 20 larian adalah kira-kira 1.5 ms. Sudah tentu, 20 larian tidak menghasilkan ukuran sampel yang mencukupi, tetapi sekarang saya ingin membuktikan. Saya ingin menjalankan ujian pada skala yang lebih besar yang dapat memberi saya jawapan yang lebih tepat. Saya tentu saja boleh pergi dan melambaikannya di wajah rakan sekerja saya untuk membuktikan bahawa mereka salah.
Mengapa kita melakukan apa yang kita buat?

Kaedah tradisional
Segera, saya mendapati diri saya berada di dalam air panas. Untuk mengukur berapa lama masa setTimeout(0)
berjalan, saya memerlukan fungsi yang:
- mengambil gambar masa sekarang
- dilaksanakan
setTimeout
- kemudian keluar dengan segera sehingga timbunan akan jelas dan panggilan balik yang dijadualkan dapat berjalan dan mengira perbezaan waktu
- dan saya memerlukan fungsi itu untuk menjalankan sebilangan besar kali sehingga pengiraannya bermakna secara statistik
Tetapi usaha membina untuk ini - untuk-gelung - tidak akan berjaya. Kerana for-loop tidak membersihkan timbunan sehingga ia melaksanakan setiap gelung, panggilan balik tidak akan berjalan dengan segera. Atau, untuk memasukkannya ke dalam kod, kami akan mendapatkannya:

Masalah di sini adalah wujud - jika saya ingin menjalankan setTimeout
berkali-kali secara automatik, saya harus melakukannya dari dalam konteks lain. Tetapi, selama saya berlari dari konteks lain, akan selalu ada kelewatan tambahan dari saat saya memulakan ujian hingga waktu panggilan balik dijalankan.
Sudah tentu saya dapat meneteskannya seperti beberapa detektif yang tidak berguna ini, menulis fungsi yang melakukan apa yang saya perlukan, dan kemudian menyalin & menampalnya 10,000 kali. Saya akan belajar apa yang ingin saya ketahui, tetapi pelaksanaannya akan jauh lebih baik. Sekiranya saya akan menggosok ini di wajah orang lain, saya lebih suka melakukannya dengan cara lain.
Kemudian datang kepada saya.
Kaedah revolusi
Saya boleh menggunakan pekerja web.
Pekerja laman web menggunakan rangkaian yang berbeza. Oleh itu, jika saya meletakkan setTimeout
logik di pekerja web, saya dapat memanggilnya berkali-kali. Setiap panggilan akan membuat konteks pelaksanaannya sendiri, memanggil setTimeout
, dan segera keluar dari fungsi sehingga panggilan balik dapat dilaksanakan. Saya tidak sabar untuk melakukan kerja dengan pekerja web.
Sudah tiba masanya untuk beralih ke Teks Sublime yang dipercayai saya.
Saya mula menguji perairan. Dengan kod ini di main.js
:

Ada yang memasang paip di sini untuk persiapan ujian sebenar, tetapi pada mulanya saya hanya ingin memastikan saya dapat berkomunikasi dengan betul dengan pekerja web. Jadi ini adalah permulaan worker.js
:

Dan walaupun ia berfungsi seperti daya tarikan - ia menghasilkan hasil yang semestinya saya harapkan, tetapi tidak:

Setelah terbiasa dengan sinkronisasi di JS, saya tidak dapat menahan diri daripada terkejut dengan perkara ini. Saat pertama saya melihat otak saya mendaftarkan bug. Tetapi, kerana setiap gelung menubuhkan pekerja web baru dan mereka berjalan secara tidak segerak, masuk akal bahawa nombor tersebut tidak akan dicetak mengikut urutan.
Mungkin mengejutkan saya, tetapi ia berfungsi seperti yang diharapkan. Saya boleh meneruskan ujian.
Apa yang saya mahukan adalah agar onmessage
fungsi pekerja web mendaftar t0
, memanggil setTimeout
, dan kemudian segera keluar agar tidak menyekat tumpukan. Akan tetapi, saya dapat memasukkan fungsi tambahan di dalam panggilan balik, setelah saya menetapkan nilai t1
. Saya menambahkan saya postMessage
ke panggilan balik, jadi ia tidak menyekat timbunan:

Dan inilah main.js
kodnya:

Versi ini mempunyai masalah.
Sudah tentu - sejak saya baru bekerja di web, saya tidak menganggapnya pada mulanya. Tetapi, ketika beberapa fungsi berjalan terus dicetak 0
, saya rasa ada yang tidak betul.
Semasa saya mencetak jumlah dari dalam, onmessage
saya mendapat jawapan saya. Fungsi utama bergerak secara serentak, dan tidak menunggu mesej dari pekerja untuk kembali, jadi ia mengira rata-rata sebelum pekerja web selesai.
Penyelesaian yang cepat dan kotor adalah dengan menambahkan pembilang dan melakukan pengiraan hanya apabila kaunter telah mencapai nilai maksimum. Jadi inilah yang barumain.js:

Dan inilah hasilnya:
main(10)
: 0.1
main(100)
: 1.41
main(1000)
: 13.082
Oh. Saya. Itu tidak bagus, bukan? Apa yang berlaku di sini?

Saya mengorbankan ujian prestasi untuk melihat ke dalam. Saya sekarang log t0
dan t1
ketika mereka dibuat, hanya untuk melihat apa yang berlaku di sana.
Dan hasilnya:

Ternyata jangkaan saya untuk t1
dikira dengan segera t0
juga sesat. Pada hakikatnya, hakikat bahawa tiada apa-apa mengenai pekerja web adalah segerak bahawa andaian saya yang paling asas mengenai bagaimana kod saya berkelakuan tidak lagi berlaku. Ini adalah titik buta yang sukar dilihat.
Bukan hanya itu, malah hasil yang saya dapat main(10)
dan main(100)
yang pada awalnya membuat saya sangat gembira dan sombong, bukanlah sesuatu yang boleh saya percayai.
Asinkronisiti pekerja web juga menjadikan mereka proksi yang tidak boleh dipercayai untuk bagaimana sesuatu bertindak dalam timbunan biasa kami. Oleh itu, semasa mengukur prestasi setTimeout
pekerja web memberikan beberapa hasil yang menarik, ini bukan hasil yang menjawab persoalan kami.
Kaedah buku teks
Saya kecewa ... bolehkah saya tidak menemui penyelesaian JS vanila yang boleh menjadi elegan dan membuktikan rakan saya salah?
Dan kemudian saya sedar - ada sesuatu yang boleh saya lakukan, tetapi saya tidak menyukainya.
Saya boleh menghubungi setTimeout
secara berulang.

Sekarang apabila saya memanggilnya, main
ia akan memanggil testRunner
mana yang mengukur t0
dan kemudian menjadualkan panggilan balik. t1
Panggilan balik kemudian dijalankan dengan serta-merta, mengira dan kemudian memanggil testRunner
semula, sehingga mencapai jumlah panggilan yang diinginkan.
Hasil kod ini sangat mengejutkan. Berikut adalah beberapa cetakan main(10)
dan main(1000)
:

Hasilnya jauh berbeza ketika memanggil fungsi 1,000 kali berbanding memanggilnya 10 kali. Saya telah mencuba ini berulang kali dan mendapat hasil yang hampir sama, dengan main(10)
masuk pada 3-4 ms, dan mencapai main(1000)
puncak 5 ms.
Sejujurnya, saya tidak pasti apa yang berlaku di sini. Saya mencari jawapan, tetapi tidak menemui penjelasan yang munasabah. Sekiranya anda membaca ini dan mempunyai tekaan yang tepat tentang apa yang berlaku - saya ingin mendengar daripada anda dalam komen.
Kaedah yang dicuba dan benar
Di suatu tempat di dalam fikiran saya, saya selalu tahu bahawa perkara ini akan terjadi ... Perkara-perkara menarik sangat bagus bagi mereka yang dapat memperolehnya, tetapi dicuba dan benar akan selalu ada pada akhirnya. Walaupun saya cuba menghindarinya, saya selalu tahu ini adalah pilihan. setInterval
.

Kod ini melakukan muslihat dengan kekuatan yang agak kasar. setInterval
menjalankan fungsi berulang kali, menunggu 50 ms antara setiap larian, untuk memastikan timbunannya jelas. Ini tidak sesuai, tetapi menguji apa yang saya perlukan.
Dan hasilnya juga menjanjikan. Masa nampaknya sesuai dengan jangkaan asal saya - di bawah 1.5ms.


Akhirnya saya boleh meletakkan beg ini di tempat tidur. Saya mempunyai beberapa pasang surut, dan bahagian hasil saya yang tidak dijangka, tetapi pada akhirnya hanya satu perkara yang penting - saya telah membuktikan pembangun lain salah! Itu cukup baik untuk saya.
Ingin bermain-main dengan kod ini? lihat di sini: //github.com/NettaB/setTimeout-test