Menurut Anda apa yang akan terjadi jika Anda menjalankan kode ini di konsol browser?
function foo() { setTimeout(foo, 0); } foo();
Dan yang ini?
function foo() { Promise.resolve().then(foo); } foo();
Jika Anda, seperti saya, telah membaca banyak artikel tentang Event Loop, Main Thread, tugas, microtasks dan banyak lagi, tetapi merasa sulit untuk menjawab pertanyaan di atas - artikel ini untuk Anda.
Jadi mari kita mulai. Kode untuk setiap halaman HTML di browser dijalankan di
Main Thread . Utas utama adalah utas utama di mana browser melakukan JS, melakukan redraw, menangani tindakan pengguna, dan banyak lagi. Pada dasarnya, ini adalah tempat mesin JS diintegrasikan ke dalam browser.
Cara termudah untuk mengetahuinya adalah dengan melihat diagram:
Gambar 1Kami melihat bahwa satu-satunya tempat di mana tugas dapat masuk ke dalam Call Stack dan selesai adalah Event Loop. Bayangkan Anda berada di tempatnya. Dan tugas Anda adalah mengimbangi tugas. Tugas dapat terdiri dari dua jenis:
- Pribadi - eksekusi kode JavaScript utama di situs (selanjutnya kami akan menganggap bahwa kode itu sudah dieksekusi)
- Tugas Pelanggan - Render, Microtasks, dan Tasks
Kemungkinan besar, tugas pribadi Anda akan diprioritaskan. Event Loop setuju dengan ini :) Tetap merampingkan tugas dari pelanggan.
Tentu saja, hal pertama yang terlintas dalam pikiran adalah untuk memberikan prioritas kepada setiap pelanggan, dan mengatur mereka. Yang kedua adalah menentukan dengan tepat bagaimana tugas dari masing-masing pelanggan akan diproses - satu per satu, sekaligus, atau mungkin dalam batch.
Lihatlah diagram ini:
Gambar 2Berdasarkan skema ini, seluruh pekerjaan Event Loop dibangun.
Setelah kami mulai menjalankan skrip apa pun, tugas dengan eksekusi skrip ini dimasukkan ke dalam antrian Tugas. Ketika kode ini dieksekusi, kami menemukan tugas dari pelanggan yang berbeda, yang dimasukkan ke dalam antrian yang sesuai. Setelah tugas eksekusi skrip selesai (tugas dari Tugas), Loop Peristiwa pergi ke Microtasks (setelah tugas dari Tugas, Loop Peristiwa mengambil tugas dari Microtasks). Loop Peristiwa mengambil tugas darinya
sampai tugas itu
berakhir . Ini berarti bahwa jika waktu penambahan mereka sama dengan waktu eksekusi mereka, maka Perulangan Peristiwa akan menyapu mereka tanpa henti.
Selanjutnya, dia pergi ke Render dan melakukan tugas-tugas darinya. Tugas-tugas dari Render dioptimalkan oleh browser, dan jika menganggap bahwa tidak perlu menggambar apa pun dalam siklus ini, maka Event Loop hanya akan melangkah lebih jauh. Selanjutnya, Perulangan Kejadian lagi mengambil tugas dari Tugas dan meminta
hanya satu, tugas pertama dalam antrian , meneruskannya ke CallStack dan melangkah lebih jauh dalam perulangan.
Jika salah satu pelanggan tidak memiliki tugas, maka Perulangan Acara hanya berjalan ke berikutnya. Dan, sebaliknya, jika tugas pelanggan membutuhkan banyak waktu, maka pelanggan yang tersisa akan menunggu giliran mereka. Dan jika tugas dari beberapa pelanggan ternyata tidak ada habisnya, maka Call Stack meluap, dan browser mulai bersumpah:
Gambar 3 Sekarang setelah kami memahami cara kerja Loop Acara, saatnya untuk mencari tahu apa yang terjadi setelah cuplikan kode dieksekusi di awal artikel ini.
function foo() { setTimeout(foo, 0); } foo();
Kita melihat bahwa fungsi foo memanggil dirinya sendiri secara rekursif melalui setTimeout di dalam, tetapi dengan setiap panggilan itu membuat tugas pelanggan Tugas. Seperti yang kita ingat, di loop Acara, ketika mengeksekusi tugas antrian dari Tugas, hanya dibutuhkan 1 tugas dalam loop. Dan kemudian ada tugas dari Microtasks dan Render. Karenanya, potongan kode ini tidak akan menyebabkan Perulangan Kejadian dan selamanya menjalankan tugasnya. Tapi itu akan memunculkan tugas baru untuk Tugas pelanggan di setiap putaran.
Mari kita coba jalankan skrip ini di browser Google Chrome. Untuk melakukan ini, saya membuat dokumen HTML sederhana dan script.js terhubung dengan potongan kode di dalamnya. Setelah membuka dokumen, buka alat pengembang dan buka tab Perfomance dan klik tombol 'mulai profiling dan muat ulang halaman':
Gambar 4Kami melihat bahwa tugas-tugas dari Tugas dilakukan satu per satu, sekitar setiap 4ms.
Pertimbangkan puzzle kedua:
function foo() { Promise.resolve().then(foo); } foo();
Di sini kita melihat hal yang sama seperti pada contoh di atas, tetapi memanggil foo menambahkan tugas dari Microtasks, dan semuanya selesai sampai selesai. Dan ini berarti bahwa sampai Loop Peristiwa menyelesaikannya, dia tidak akan dapat pindah ke pelanggan berikutnya :( Dan kita kembali melihat
gambar sedih.
Lihatlah ini di alat pengembangan:
Gambar 5Kami melihat bahwa mikrotasks dieksekusi kira-kira sekali setiap 0,1 ms, dan ini 40 kali lebih cepat dari antrian Tugas. Semua karena semuanya dilakukan sekaligus. Dalam contoh kita, antrian bergerak tanpa henti. Untuk visualisasi, saya menguranginya menjadi 100.000 iterasi.
Itu saja!
Saya harap artikel ini bermanfaat bagi Anda, dan sekarang Anda memahami cara kerja Event Loop dan apa yang terjadi dalam contoh kode di atas.
Sampai jumpa :) Dan sampai jumpa. Jika Anda menyukainya, sukai dan berlangganan ke saluran saya :)