Apakah Anda kenal Ivan Tulup? Kemungkinan besar ya, Anda belum tahu orang macam apa ini, dan Anda harus sangat memperhatikan kondisi sistem kardiovaskularnya.
Tentang ini dan bagaimana asinkronisme bekerja di JS di bawah tenda, bagaimana Event Loop bekerja di browser dan Node.js, apakah ada perbedaan dan mungkin hal-hal serupa diberitahukan oleh
Mikhail Bashurov (
SaitoNakamura ) dalam laporannya tentang RIT ++. Kami senang berbagi dengan Anda transkrip presentasi informatif ini.
Tentang pembicara: Mikhail Bashurov adalah pengembang web fullstack di JS dan .NET dari Luxoft. Dia mencintai UI yang indah, tes hijau, transpilasi, kompilasi, teknik penyusun yang memungkinkan dan meningkatkan pengalaman dev.
Catatan Editor: Laporan Mikhail didampingi tidak hanya oleh slide, tetapi oleh proyek demo di mana Anda dapat mengklik tombol dan secara mandiri menyaksikan pelaksanaan shuffles. Opsi terbaik adalah membuka
presentasi di tab yang berdekatan dan merujuknya secara berkala, tetapi teks juga akan menyediakan tautan ke halaman tertentu. Dan sekarang kita melewati lantai ke pembicara, selamat membaca.
Kakek Ivan Tulup
Saya memiliki pencalonan untuk Ivan Tulup.

Tapi saya memutuskan untuk mengambil jalan yang lebih konformis, jadi temui - kakek Ivan Tulup!

Faktanya, hanya dua hal yang perlu diketahui tentang dia:
- Dia suka bermain kartu.
- Dia, seperti semua orang, memiliki hati, dan itu berdetak.
Fakta Serangan Jantung
Anda mungkin pernah mendengar bahwa kasus penyakit jantung dan kematian dari mereka baru-baru ini menjadi lebih sering. Mungkin penyakit jantung yang paling umum adalah serangan jantung, yaitu serangan jantung.
Apa yang menarik tentang serangan jantung?
- Paling sering, itu terjadi pada Senin pagi.
- Pada orang lajang, risiko serangan jantung dua kali lebih tinggi. Di sini, mungkin, intinya semata-mata dalam korelasi, dan bukan dalam hubungan kausal. Sayangnya (atau untungnya), bagaimanapun, ini benar.
- Sepuluh konduktor meninggal karena serangan jantung selama melakukan (pekerjaan tampaknya sangat gugup!).
- Serangan jantung adalah nekrosis otot jantung yang disebabkan oleh kurangnya aliran darah.
Kami memiliki arteri koroner yang membawa darah ke otot (miokardium). Jika darah mulai mengalir dengan buruk, otot secara bertahap mati. Secara alami, ini memiliki efek yang sangat negatif pada jantung dan pekerjaannya.
Kakek Ivan Tulup juga memiliki hati, dan berdetak. Tetapi hati kita memompa darah, dan hati Ivan Tulup memompa kode dan kesedihan kita.
Tasky: lingkaran besar sirkulasi darah
Apa tugasnya? Apa yang biasanya malas di browser? Mengapa mereka dibutuhkan sama sekali?
Misalnya, kami mengeksekusi kode dari skrip. Ini adalah satu detak jantung, dan sekarang kami memiliki aliran darah. Kami mengklik tombol dan berlangganan acara - pengendali acara untuk acara ini - Callback yang kami kirimkan. Mereka mengatur TimeTime, Callback bekerja - tugas lain. Dan dalam beberapa bagian, satu detak jantung adalah satu tugas.

Ada banyak sumber kol yang berbeda, sesuai spesifikasi ada sejumlah besar. Jantung kita terus berdetak, dan sementara itu berdetak, semuanya baik-baik saja dengan kita.
Perulangan Acara di browser: versi sederhana
Ini dapat direpresentasikan dalam diagram yang sangat sederhana.

- Ada tugas, kami sudah menyelesaikannya.
- Kemudian kita menjalankan render browser.
Tetapi pada kenyataannya, ini tidak perlu, karena dalam beberapa kasus browser mungkin tidak membuat antara dua tugas.
Ini dapat terjadi, misalnya, jika browser dapat memutuskan untuk mengelompokkan beberapa waktu tunggu atau beberapa acara gulir. Atau pada titik tertentu, ada yang tidak beres, dan browser memutuskan alih-alih 60 fps (frame rate biasa sehingga semuanya menjadi dingin dan lancar) untuk menampilkan 30 fps. Dengan demikian, ia akan memiliki lebih banyak waktu untuk mengeksekusi kode Anda dan pekerjaan bermanfaat lainnya, ia akan dapat melakukan beberapa kejutan.
Oleh karena itu, render tidak benar-benar dilakukan setelah setiap tugas.
Tasky: klasifikasi
Ada dua jenis operasi potensial:
- I / O terikat;
- Terikat CPU.
Ikatan CPU adalah pekerjaan bermanfaat yang kami lakukan (percaya, menampilkan, dll.)
I / O terikat adalah poin di mana kita dapat berbagi tugas. Itu bisa:
Kami membuat setTimeout 5000 ms, dan kami hanya menunggu 5000 ms ini, tetapi kami dapat melakukan pekerjaan lain yang bermanfaat. Hanya ketika waktu ini berlalu, kami mendapatkan Callback, dan melakukan beberapa pekerjaan di dalamnya.
Kami online. Sementara kami menunggu tanggapan dari jaringan, kami hanya menunggu, tetapi kami juga dapat melakukan sesuatu yang bermanfaat.
Atau, misalnya, kita masuk ke Network BD. Kita berbicara tentang Node.js juga, termasuk, dan jika kita ingin pergi ke suatu tempat ke jaringan dari Node.js tolong - ini adalah potensi tugas I / O terikat yang sama (input / output).
Baca file - berpotensi bukan tugas yang terikat CPU sama sekali. Di Node.js, ini berjalan di thread pool karena API Linux sedikit bengkok, jujur.
Maka CPUbound adalah:
- Sebagai contoh, ketika kita melakukan for / for (;;) loop atau kita melewati array entah bagaimana menggunakan metode tambahan: filter, map, dll.
- JSON.parse atau JSON.stringify, mis. Serialisasi pesan / deserialisasi. Ini semua dilakukan pada CPU, kita tidak bisa menunggu semuanya dieksekusi secara ajaib di suatu tempat.
- Menghitung hash, mis., Penambangan kripto.
Tentu saja, crypto juga dapat ditambang di GPU, tapi saya pikir - GPU, CPU - Anda mengerti analogi ini.
Tasky: aritmia dan trombus
Sebagai hasilnya, ternyata jantung kita berdetak: ia melakukan satu tugas, yang kedua, yang ketiga - sampai kita melakukan sesuatu yang salah. Misalnya, kita melalui array 1 juta elemen dan menghitung jumlahnya. Tampaknya ini tidak begitu sulit, tetapi dapat mengambil waktu nyata. Jika kami terus-menerus mengambil waktu nyata tanpa melepaskan tugas, render kami tidak dapat dilakukan. Dia melayang dalam kerinduan ini, dan semua - aritmia dimulai.
Saya pikir semua orang mengerti bahwa aritmia adalah penyakit jantung yang agak tidak menyenangkan. Tapi kamu masih bisa hidup bersamanya. Apa yang terjadi jika Anda menempatkan tugas yang hanya menggantung seluruh Loop Peristiwa dalam loop tanpa akhir? Anda semacam meletakkan gumpalan darah di koroner atau arteri lainnya, dan semuanya akan menjadi benar-benar sedih. Sayangnya, kakek kami Ivan Tulup akan mati.
Jadi kakek Ivan meninggal ...

Bagi kami, ini berarti bahwa seluruh tab membeku sama sekali - Anda tidak dapat mengklik apa pun, dan kemudian Chrome berkata: "Aduh, Snap!"
Ini bahkan jauh lebih buruk daripada bug situs web ketika terjadi kesalahan. Tetapi jika semuanya tergantung, dan bahkan, mungkin, CPU dimuat dan pengguna biasanya digantung, maka kemungkinan besar dia tidak akan pernah pergi ke situs Anda lagi.
Oleh karena itu, idenya adalah ini: kita memiliki tugas, dan kita tidak perlu bertahan dalam tugas ini untuk waktu yang sangat lama. Kita perlu dengan cepat melepaskannya, sehingga peramban, jika ada, dapat merender (jika mau). Jika Anda tidak mau - bagus, menari!
Demo Philip Roberts: Pembesar oleh Philip Roberts
Pertimbangkan
sebuah contoh :
$.on('button', 'click', function onClick(){ console.log('click'); }); setTimeout(function timeout() { console log("timeout"); }. 5000); console.log(“Hello world");
Intinya adalah ini: kami memiliki tombol, kami berlangganan (addEventListener), Timeout dipanggil selama 5 detik dan segera di console.log kita menulis "Halo, dunia!", Di setTimeout kita menulis Timeout, di onClick kita menulis Klik.
Apa yang akan terjadi jika kita menjalankannya dan berkali-kali kita mengklik tombol - kapan Timeout sebenarnya akan dieksekusi? Mari kita lihat demo:
Kode mulai dijalankan, masuk stack, Timeout berjalan. Sementara itu, kami mengklik tombol. Di bagian bawah antrian, beberapa acara ditambahkan. Ketika Click sedang berjalan, Timeout menunggu, meskipun 5 detik telah berlalu.
Di sini, onClick cepat, tetapi jika Anda menempatkan tugas yang lebih panjang, maka semuanya akan membeku, seperti yang sudah dijelaskan. Ini adalah contoh yang sangat sederhana. Ini satu putaran, tetapi di browser, pada kenyataannya, semuanya tidak demikian.
Dalam urutan apa acara dijalankan - apa spesifikasi HTML katakan?
Dia mengatakan yang berikut: kami memiliki 2 konsep:
- sumber tugas;
- antrian tugas.
Sumber tugas adalah sejenis tugas. Ini mungkin interaksi Pengguna, yaitu, onClick, onChange - sesuatu yang berinteraksi dengan pengguna; atau timer, mis. setTimeout dan setInterval, atau PostMessages; atau bahkan tipe yang benar-benar liar seperti sumber tugas Canvas Blob Serialisasi - juga tipe yang terpisah.
Spesifikasi mengatakan bahwa untuk sumber tugas yang sama tugas akan dijamin akan dieksekusi dalam urutan yang ditambahkan. Untuk yang lainnya, tidak ada yang dijamin, karena dapat ada jumlah tugas yang tidak terbatas. Browser memutuskan berapa banyak akan ada. Dengan bantuan antrian tugas dan pembuatannya, browser dapat memprioritaskan tugas tertentu.
Prioritas Browser dan Antrean Tugas

Bayangkan kita memiliki 3 baris:
- interaksi pengguna;
- batas waktu
- memposting pesan.
Browser mulai mendapatkan tugas dari antrian ini:
- Pertama, dia mengambil interaksi pengguna Fokus - ini sangat penting - satu detak jantung telah hilang.
- Lalu ia mengambil postMessage - well, postMessages adalah prioritas yang cukup tinggi, keren!
- Yang berikutnya, onChange, juga kembali dari interaksi pengguna dalam prioritas.
- Selanjutnya, Klik dikirim. Antrian interaksi pengguna telah berakhir, kami telah menampilkan kepada pengguna segala yang diperlukan.
- Lalu kita ambil setInterval , tambahkan postMessages.
- setTimeout hanya akan menjalankan yang terbaru . Dia berada di suatu tempat di ujung garis.
Sekali lagi ini adalah contoh yang sangat sederhana, dan, sayangnya,
tidak ada yang bisa menjamin bagaimana ini akan bekerja di browser , karena mereka memutuskan semua ini sendiri. Anda perlu menguji ini sendiri jika Anda ingin mengetahui apa itu.
Sebagai contoh, postMessages lebih diutamakan daripada setTimeout. Anda mungkin pernah mendengar hal seperti setImmediate, yang, misalnya, di browser IE, hanya asli. Tetapi ada polyfile yang terutama didasarkan bukan pada setTimeout, tetapi pada pembuatan saluran postMessages dan berlangganan. Ini umumnya bekerja lebih cepat karena browser memprioritaskannya.
Nah, tugas-tugas ini dilakukan. Pada titik apa kita menyelesaikan tugas kita dan memahami bahwa kita dapat mengambil yang berikutnya, atau yang dapat kita render?
Tumpukan
Tumpukan adalah struktur data sederhana yang bekerja berdasarkan prinsip "last in - first out", mis. "Aku yang terakhir - kamu yang pertama
. " Rekan terdekat yang mungkin terdekat adalah setumpuk kartu. Karena itu, kakek kami Ivan Tulup suka bermain kartu.

Contoh di atas, di mana ada beberapa kode, contoh yang sama dapat dicubit dalam
presentasi . Di beberapa tempat kami memanggil handleClick, masukkan console.log, panggil showPopup dan jendela. konfirmasi. Mari kita membentuk tumpukan.
- Jadi, pertama-tama kita ambil handleClick dan dorong panggilan ke fungsi ini ke stack - hebat!
- Lalu kita masuk ke tubuhnya dan melaksanakannya.
- Kami meletakkan console.log di tumpukan dan segera menjalankannya, karena semuanya ada untuk menjalankannya.
- Selanjutnya kita taruh showConfirm - ini adalah panggilan fungsi - hebat.
- Kami menempatkan fungsi pada tumpukan - kami menempatkan tubuhnya, yaitu, window.confirm.
Kami tidak memiliki apa-apa lagi - kami melakukannya. Sebuah jendela akan muncul: "Apakah Anda yakin?", Klik "Ya", dan semuanya akan meninggalkan tumpukan. Sekarang kita telah menyelesaikan tubuh showConfirm dan badan handleClick. Tumpukan kami dihapus dan kami dapat melanjutkan ke tugas berikutnya. Pertanyaan: Oke, sekarang saya tahu bahwa Anda perlu memecahnya menjadi potongan-potongan kecil. Bagaimana saya bisa, misalnya, melakukan ini dalam kasus yang paling dasar?
Mempartisi sebuah array menjadi potongan dan memprosesnya secara tidak sinkron
Mari kita lihat contoh yang paling "dahi". Saya segera memperingatkan Anda: tolong jangan mencoba untuk mengulanginya di rumah - itu tidak akan dikompilasi.

Kami memiliki array besar, besar, dan kami ingin menghitung sesuatu berdasarkan itu, misalnya, untuk mem-parsing beberapa data biner. Kita cukup memecahnya menjadi potongan: proses bagian ini, ini dan ini. Kami memilih ukuran chunk, misalnya, 10 ribu elemen, kami mempertimbangkan berapa banyak chunk yang akan kami miliki. Kami memiliki fungsi parseData yang masuk ke CPU terikat dan benar-benar dapat melakukan sesuatu yang berat. Kemudian kita memecah array menjadi potongan-potongan, lakukan setTimeout (() => parseData (slice), 0).
Dalam hal ini, browser akan dapat kembali memprioritaskan interaksi pengguna dan membuat di antaranya. Artinya, Anda setidaknya melepaskan Loop Peristiwa Anda, dan itu terus bekerja. Jantungmu terus berdetak, dan itu bagus.
Tapi ini benar-benar contoh yang sangat "dahi". Ada banyak API di browser untuk membantu Anda melakukan ini dengan cara yang lebih khusus.
Selain setTimeout dan setInterval, ada API yang melampaui batas, seperti, misalnya, requestAnimationFrame dan requestIdleCallback.
Mungkin banyak yang akrab dengan
requestAnimationFrame , dan bahkan sudah menggunakannya. Itu dieksekusi sebelum rendering. Pesonanya adalah, pertama, ia mencoba mengeksekusi setiap 60 fps (atau 30 fps), dan kedua, semua ini dilakukan segera sebelum membuat Model Objek CSS, dll.

Oleh karena itu, bahkan jika Anda memiliki beberapa requestAnimationFrame, mereka sebenarnya akan mengelompokkan semua perubahan, dan bingkai akan keluar lengkap. Dalam hal setTimeout, Anda tentu tidak bisa mendapatkan jaminan seperti itu. Satu setTimeout akan mengubah satu hal, yang lain lagi, dan di antara proses rendering mungkin tergelincir - Anda akan menyentak layar atau sesuatu yang lain. RequestAnimationFrame sangat bagus untuk ini.
Selain itu, ada juga
requestIdleCallback. Mungkin Anda mendengar bahwa ini digunakan dalam React v16.0 (Fiber). RequestIdleCallback berfungsi sedemikian rupa sehingga jika browser memahami bahwa ada waktu di antara frame (60 fps) untuk melakukan sesuatu yang bermanfaat, dan pada saat yang sama mereka sudah melakukan semuanya - mereka melakukan tugas, requestAnimationFrame lakukan - sepertinya keren, maka itu dapat menghasilkan kuanta kecil, katakanlah, masing-masing 50 ms, sehingga Anda dapat melakukan sesuatu (mode IDLE).
Itu tidak ada dalam diagram di atas, karena tidak terletak di tempat tertentu. Browser dapat memutuskan untuk meletakkannya di depan frame, setelah frame, antara requestAnimationFrame dan render, setelah tugas, sebelum tugas. Tidak ada yang bisa menjamin ini.
Dijamin untuk Anda bahwa jika Anda memiliki pekerjaan yang tidak terkait dengan mengubah DOM (karena kemudian requestAnimationFrame adalah animasi dan sebagainya), sementara itu bukan prioritas super, tetapi nyata, maka requestIdleCallback adalah jalan keluar Anda.
Jadi, jika kita memiliki operasi terikat CPU yang panjang, maka kita dapat mencoba memecahnya menjadi beberapa bagian.
- Jika ini adalah perubahan DOM, maka gunakan requestAnimationFrame.
- Jika ini adalah tugas yang tidak diprioritaskan, berumur pendek dan tidak sulit yang tidak akan membebani CPU, maka requestIdleCallback.
- Jika kami memiliki tugas besar yang sangat kuat yang perlu dilakukan terus-menerus, maka kami melampaui Event Loop dan menggunakan WebWorkers. Tidak ada jalan lain.
Tugas di browser:- Hancurkan semuanya menjadi tugas-tugas kecil.
- Ada banyak jenis tugas.
- Tugas diprioritaskan oleh tipe-tipe ini melalui antrian spesifikasi.
- Banyak yang diputuskan oleh browser, dan satu-satunya cara untuk memahami cara kerjanya adalah dengan hanya memeriksa apakah satu atau kode lain sedang berjalan.
- Namun spesifikasinya tidak selalu dihormati!
Masalahnya adalah bahwa Ivan Tulup kami adalah kakek tua, karena implementasi Event Loop di browser juga sebenarnya sangat tua. Mereka dibuat sebelum spesifikasi ditulis, sehingga spesifikasi, sayangnya, dihormati sejauh. Bahkan jika Anda membaca seperti apa spesifikasi itu, tidak ada yang menjamin bahwa semua browser mendukungnya. Jadi pastikan untuk memeriksa di browser bagaimana ini sebenarnya bekerja.
Kakek Ivan Tulup di browser adalah orang yang tidak dapat diprediksi, dengan beberapa fitur menarik, Anda perlu mengingat ini.
Terminator Santa: Mascot Loop di Node.js
Node.js lebih seperti seseorang seperti itu.

Karena di satu sisi itu adalah kakek yang sama dengan janggut, tetapi pada saat yang sama semuanya dibagikan secara bertahap dan dicat dengan jelas di mana dilakukan.
Fase-fase Perulangan Acara di Node.js:- pengatur waktu;
- panggilan balik yang tertunda;
- menganggur, siapkan;
- jajak pendapat;
- periksa;
- tutup panggilan balik.
Semuanya kecuali yang terakhir tidak begitu jelas apa artinya. Fase memiliki nama aneh seperti itu, karena di bawah tenda, seperti yang sudah kita ketahui, kita memiliki Libuv untuk memerintah semua orang:
- Linux - epoll / POSIX AIO;
- BSD - kqueue;
- Windows - IOCP;
- Solaris - port acara.
Ribuan dari mereka semua!
Selain itu, Libuv juga menyediakan Perulangan Acara yang sama. Tidak memiliki spesifikasi Node.js, tetapi ada beberapa fase, dan Node.js hanya menggunakannya. Tetapi karena suatu alasan dia mengambil nama-nama itu dari sana.
Mari kita lihat apa arti setiap fase sebenarnya.
Tahap Timers melakukan:
- Timer siap panggil balik;
- setTimeout dan setInterval;
- Tapi BUKAN setImmediate adalah fase yang berbeda.
Fase panggilan balik yang tertunda
Sebelum ini, fase dokumentasi disebut callback I / O. Baru-baru ini, dokumentasi ini diperbaiki, dan tidak lagi bertentangan dengan dirinya sendiri. Sebelum ini, di satu tempat ada tertulis bahwa I / O callback dieksekusi dalam fase ini, di tempat lain - yang dalam fase polling. Tapi sekarang semuanya ditulis di sana dengan tegas dan baik, jadi bacalah dokumentasinya - sesuatu akan menjadi lebih mudah dimengerti.
Dalam fase panggilan balik yang tertunda, panggilan balik dari beberapa operasi sistem (kesalahan TCP) dieksekusi. Artinya, jika di Unix ada kesalahan pada TCP-socket, dalam hal ini ia tidak ingin segera membuangnya, tetapi dalam callback, yang akan dieksekusi hanya pada fase ini. Hanya itu yang perlu kita ketahui tentangnya. Kami praktis tidak tertarik.
Phase Idle, siapkan
Dalam fase ini, kita tidak bisa melakukan apa pun, jadi kita akan melupakannya secara prinsip.

Fase pemilihan
Ini adalah fase paling menarik di Node.js karena ia melakukan pekerjaan bermanfaat utama:
- Melakukan panggilan balik I / O (bukan fase panggilan balik yang tertunda!).
- Menunggu acara dari I / O;
- Sangat keren untuk melakukan setImmediate;
- Tidak ada timer;
Ke depan, setImmediate akan dieksekusi pada fase pemeriksaan berikutnya, yaitu dijamin sebelum timer.
Dan juga fase polling mengontrol aliran acara. Misalnya, jika kita tidak memiliki penghitung waktu, tidak ada setImmediate, yaitu, tidak ada yang melakukan timer, setImmediate tidak menelepon, kita hanya memblokir dalam fase ini dan menunggu acara dari I / O, jika sesuatu datang kepada kita, jika ada panggilan balik jika kami mendaftar untuk sesuatu.
Bagaimana model non-pemblokiran diterapkan? Misalnya, di Epoll yang sama, kita dapat berlangganan ke suatu acara - buka soket dan tunggu sesuatu dituliskan padanya. Selain itu, argumen kedua adalah batas waktu, mis. kita akan menunggu Epoll, tetapi jika batas waktu berakhir, dan acara dari I / O tidak datang, maka itu akan keluar dari batas waktu. Jika suatu peristiwa datang kepada kita dari jaringan (seseorang menulis ke soket), maka itu akan datang.
Oleh karena itu, fase jajak pendapat mengambil callback paling awal dari heap (heap adalah struktur data yang memungkinkan pengiriman dan pengiriman yang diurutkan dengan baik), mengambil batas waktu, menulis ke batas waktu ini dan melepaskan semuanya. Jadi, bahkan jika tidak ada yang menulis kepada kami di soket, batas waktu akan berfungsi, kembali ke fase pemilihan dan pekerjaan akan dilanjutkan.
Penting untuk dicatat bahwa dalam fase polling ada batasan jumlah panggilan balik pada suatu waktu.
Sangat menyedihkan bahwa dalam fase yang tersisa tidak. Jika Anda menambahkan 10 miliar batas waktu, Anda menambahkan batas waktu 10 miliar. Oleh karena itu, fase selanjutnya adalah fase periksa.
Periksa fase
Di sinilah setImmediate dijalankan. Fase indah di setImmediate itu, yang disebut dalam fase polling, dijamin untuk dieksekusi lebih awal dari timer. Karena timer hanya akan berada pada tick berikutnya di awal, dan lebih awal dari fase polling. Oleh karena itu, kita tidak boleh takut berkompetisi dengan timer lain dan menggunakan fase ini untuk hal-hal yang tidak kita inginkan untuk alasan tertentu untuk dieksekusi dalam panggilan balik.
Panggilan balik fase dekat
Fase ini tidak menjalankan semua panggilan balik penutup soket kami dan jenis lainnya:
socket.on('close', …).
Dia mengeksekusi mereka hanya jika acara ini terbang secara tak terduga, misalnya, seseorang di ujung lainnya mengirim: "Semuanya - tutup soket - pergi dari sini, Vasya!" Maka fase ini akan berhasil, karena acara tidak terduga. Tapi ini tidak terlalu mempengaruhi kita.
Pemrosesan potongan asinkron yang salah di Node.js
Apa yang akan terjadi jika kita meletakkan pola yang sama yang kita ambil di browser dengan setTimeout di Node.js - yaitu, kita membagi array menjadi potongan-potongan, untuk setiap potongan kita membuat setTimeout - 0.
const bigArray = [1..1_000_000] const chunks = getChunks(bigArray) const parseData = (slice) =>
Apakah Anda pikir ada masalah dengan ini?
Saya sudah berlari sedikit ketika saya mengatakan bahwa jika Anda menambahkan 10 ribu batas waktu (atau 10 miliar!), Akan ada 10 ribu penghitung waktu dalam antrian, dan dia akan mendapatkan dan mengeksekusi mereka - tidak ada perlindungan dari ini: dapatkan - jalankan, dapatkan - untuk memenuhi dan seterusnya ad infinitum.
Hanya fase polling, jika kita secara konstan mendapatkan event dari I / O, setiap saat seseorang menulis sesuatu di socket sehingga kita dapat mengeksekusi setidaknya timer dan setImmediate, ia memiliki batasan perlindungan, dan itu tergantung pada sistem. Artinya, itu akan berbeda pada sistem operasi yang berbeda.
Sayangnya, fase-fase lain, termasuk timer dan setImmediate,
tidak memiliki perlindungan seperti itu. Karena itu, jika Anda melakukan seperti pada contoh, semuanya akan membeku dan tidak akan mencapai fase polling untuk waktu yang sangat lama.
Tetapi apakah Anda berpikir bahwa sesuatu akan berubah jika kami mengganti setTimeout (() => parseData (slice), 0) dengan setImmediate (() => parseData (slice))? - Tentu, tidak, tidak ada perlindungan pada tahap pemeriksaan di sana.
Untuk mengatasi masalah ini, Anda dapat memanggil
pemrosesan rekursif .
const parseData = (slice) =>
Intinya adalah bahwa kita mengambil fungsi parseData dan menulis panggilan rekursifnya, tetapi bukan hanya diri kita sendiri, tetapi melalui setImmediate. Ketika Anda memanggil ini dalam fase setImmediate, itu akan ke centang berikutnya, dan bukan ke yang sekarang. Oleh karena itu, ini akan merilis Loop Peristiwa, itu akan melangkah lebih jauh dalam lingkaran. Artinya, kita memiliki recursiveAsyncParseData, di mana kita melewati indeks tertentu, dapatkan potongan dengan indeks ini, uraikan - dan kemudian masukkan antrian setImmediate dengan indeks berikutnya. Ini akan sampai ke tick kita berikutnya dan kita bisa memproses semua ini secara rekursif.
Benar, masalahnya adalah ini masih semacam tugas yang terikat CPU. Mungkin dia masih akan menimbang dan mengambil waktu di Event Loop. Kemungkinan besar Anda ingin Node.js Anda murni terikat I / O.
Oleh karena itu, lebih baik menggunakan beberapa hal lain, misalnya,
proses fork / thread pool.Sekarang kita tahu tentang Node.js bahwa:
- semuanya didistribusikan secara bertahap - yah, kita jelas tahu ini;
- ada perlindungan terhadap fase polling yang terlalu lama, tetapi tidak sisanya;
- pola pemrosesan rekursif dapat diterapkan agar tidak memblokir Event Loop;
- Tetapi lebih baik menggunakan garpu proses, pool thread, proses anak
Anda juga harus berhati-hati dengan kumpulan utas, karena Node.js memulai sesuatu di sana, khususnya, penyelesaian DNS, karena untuk Linux, untuk beberapa alasan, fungsi penyelesaian DNS tidak asinkron. Oleh karena itu, itu harus dijalankan di ThreadPool. Di Windows, untungnya, tidak begitu. Tetapi di sana Anda dapat membaca file secara tidak sinkron. Di Linux, sayangnya, itu tidak mungkin.
Menurut pendapat saya, batas standar adalah 4 proses di ThreadPool. Karena itu, jika Anda aktif melakukan sesuatu di sana, itu akan bersaing dengan orang lain - dengan fs dan lainnya. Anda dapat mempertimbangkan meningkatkan ThreadPool, tetapi juga dengan sangat hati-hati. Jadi baca sesuatu tentang topik ini.
Microtask: sirkulasi paru-paru
Kami memiliki tugas di Node.js dan tugas di browser. Anda mungkin sudah pernah mendengar tentang mikrotask. Mari kita lihat apa itu dan bagaimana cara kerjanya, dan mulai dengan browser.
Microtask di browser
Untuk memahami cara kerja mikrotask, kita beralih ke algoritma acara loop sesuai dengan standar whatwg, yaitu, mari kita pergi ke spesifikasi dan melihat bagaimana semuanya terlihat.

Menerjemahkan ke dalam bahasa manusia, tampilannya seperti ini:
- Ambil tugas gratis dari baris kami
- Kami melaksanakannya
- Kami melakukan pemeriksaan mikrotask - OK, kami masih tidak tahu apa itu, tapi kami ingat itu.
- Kami memperbarui rendering (jika perlu), dan kembali ke titik awal.

Mereka dilakukan di tempat yang ditunjukkan pada diagram, dan di beberapa tempat lagi, yang akan segera kita pelajari. Artinya, tugas selesai, mikrotask dieksekusi.
Sumber microtucks
Penting - bukan Janji itu sendiri, yaitu Janji. Panggilan balik yang ditempatkan kemudian adalah mikrotask. Jika Anda menelepon 10 maka - Anda memiliki 10 mikrokars, 10 ribu kemudian - 10 ribu mikrokar.
- Pengamat mutasi.
- Object.observe , yang sudah usang dan tidak ada yang perlu.
Berapa banyak yang menggunakan pengamat Mutasi?
Saya pikir sedikit yang menggunakan pengamat Mutasi. Kemungkinan besar, Promise. Kemudian digunakan lebih banyak, itu sebabnya kami akan mempertimbangkannya dalam contoh.
Fitur pos pemeriksaan mikrotask:- Kami melakukan segalanya - ini berarti kami melakukan semua mikrotasks yang kami miliki dalam antrian sampai akhir. Kami tidak melepaskan apa pun - kami hanya mengambil dan melakukan semua itu, mereka harus mikro, kan?
- Anda masih dapat menghasilkan mikrotask baru dalam proses, dan mereka akan dieksekusi di pos pemeriksaan mikrotask yang sama.
- Yang juga penting - mereka dieksekusi tidak hanya setelah eksekusi tugas, tetapi juga setelah membersihkan tumpukan.
Ini adalah hal yang menarik. Ternyata dimungkinkan untuk menghasilkan mikrotasks baru dan kita semua akan memenuhinya semua. Apa yang bisa membawa kita ke sini?

Kami memiliki dua hati. Saya menghidupkan hati pertama dengan animasi JS, dan yang kedua dengan animasi CSS. Ada fitur hebat lain yang disebut starveMicrotasks. Kami menyebutnya Promise.resolve, dan kemudian menempatkan fungsi yang sama di kemudian.
Lihat di
presentasi apa yang terjadi jika Anda memanggil fungsi ini.
Ya, jantung JS akan berhenti, karena kami menambahkan mikrotask, dan kemudian kami menambahkan mikrotask di dalamnya, dan kemudian kami menambahkan mikrotask di dalamnya ... Dan tanpa henti.
Artinya, panggilan microtucks rekursif akan menggantung segalanya. Tapi sepertinya saya memiliki semuanya asinkron! Seharusnya dilepaskan, saya menelepon setTimeout di sana. Tidak! Sayangnya, Anda perlu berhati-hati dengan microtask, jadi jika Anda entah bagaimana menggunakan panggilan rekursif, berhati-hatilah - Anda dapat memblokir semuanya.
Selain itu, seperti yang kita ingat, microtask dieksekusi di akhir pembersihan tumpukan. Kami ingat apa itu tumpukan. Ternyata segera setelah kami keluar dari kode kami, panggilan balik setTimeout dieksekusi - itu saja - mikrotasks langsung ke sana. Ini dapat menyebabkan efek samping yang menarik.
Pertimbangkan
sebuah contoh .

Ada tombol dan wadah abu-abu di mana ia berada. Kami berlangganan klik tombol dan wadah. , , , .
2 :
- Promise.resolve;
- .then, console.log('RO')
«FUS», – «DAH!» ( ).
, ? , , «FUS RO DAH!» Hebat! , .

, , . – . , - ?

! .

, .
, , , . ,
.
- — buttonHandleClick, .
- Promise.resolve. . , console.log('RO') . .
- console.log('FUS').
- buttonHandleClick . .
- , (divHandleClick) , «DAH!».
- HandleClick .
, . ?
:
- button.click(). .
- button HandleClick.
- Promise.resolve then. , Promise.resolve .
- console.log «FUS».
- buttonHandleClick , .
(click) , , . divHandleClick , , console.log('DAH!') . , .
, , button.click .
. , , . , , .
: () ( ). - , , stopPropagation. , , , , - , .
, - ( junior-) — «», promise, , then , - . ,
, : , , . . , - .
( 4) , . , , , , - . .
, :- Event Loop. Ini tidak menyenangkan.
- , .
, . — , , .
Node.js
Node.js Promise.then process.nextTick. , — . , , , , .
process.nextTick
, process.nextTick, setImmediate? Node.js ?
. createServer, EventEmitter, , listen ( ), .
const createServer = () => { const evEmitter = new EventEmitter() return { listen: port => { evEmitter.emit('listening', port) return evEmitter } } } const server = createServer().listen(8080) server.on('listening', () => console.log('listening'))
, , 8080, listening console.log - .
, , - .
createServer, . listen, , . .
, , . ? process.nextTick: evEmitter.emit('listening', port) process.nextTick(() => evEmitter.emit('listening', port)).
,
process.nextTick , . EventEmitter, . , , API, . process.nextTick, emit , userland . createServer, , listen, listening. — process.nextTick — ! , , .
process.nextTick . , .
, process.nextTick , Promise.then . process.nextTick , — , Event Loop, Node.js. , , .
process.nextTick , ghbvtybnm setImmediate , C++ .. process.nextTick .
Async/await
API — async/await, - . . , async/await Promise, Event Loop . , .
, !Frontend Conf — 4 5 , . , :
Ayo, ini akan menarik!