Gelung acara (event loop) JavaScript yang menggerakkan halaman web anda berkelakuan sangat berbeza daripada gelung yang menggerakkan pelayan Node.js, dan ketidakpadanan ini boleh membekukan UI atau menyekat I/O jika anda tidak berhati-hati. Mengetahui di mana kedua-duanya berbeza adalah penting bagi sesiapa sahaja yang menulis kod asinkronus yang berjalan dalam kedua-dua persekitaran.
Mengapa perbezaan ini penting
Gelung acara tidak ditakrifkan oleh spesifikasi ECMAScript; ia wujud dalam hos. Pelayar mesti memastikan halaman kekal responsif semasa merender bingkai (frames), manakala Node.js dibina berasaskan I/O bukan-sekatan (non-blocking I/O). Mencampuradukkan corak yang berfungsi dalam satu hos dengan hos yang lain boleh menghasilkan pepijat yang sukar untuk dihasilkan semula: rantaian janji (promises) yang panjang boleh melambatkan proses lukis semula (repaint) pelayar, manakala gelung process.nextTick yang tidak diperiksa boleh menghalang Node daripada mencapai fasa I/O nya.
Gelung berasaskan giliran pelayar
Dalam pelayar, gelung menjalankan satu kitaran tunggal yang menyelang-nyelikan pelaksanaan tugas, pengosongan mikrotugasan (microtask draining), dan perenderaan (rendering):
- Jalankan satu makrotugasan (pengendali klik,
setTimeout, dll.). - Kosongkan semua mikrotugasan (promises,
queueMicrotask). - Jika tiba masa untuk bingkai, lukis (paint) dan komposisi untuk mencapai sasaran 60 fps.
- Ulang.
Dua API memberikan pengatur cara cangkuk (hooks) eksplisit ke dalam kitaran ini:
requestAnimationFrame– dipanggil sejurus sebelum pelayar melukis. Ia adalah tempat yang betul untuk kerja animasi kerana panggilan balik (callback) dijalankan selepas mikrotugasan semasa tetapi sebelum bingkai seterusnya.requestIdleCallback– dipanggil apabila pelayar tidak mempunyai kerja berkeutamaan tinggi. Ia berguna untuk tugas berimpak rendah seperti analitik atau pra-pemuatan data.
Perangkap: kebuluran mikrotugasan (microtask starvation)
Oleh kerana pelayar mengosongkan baris gilir mikrotugasan sebelum ia merender, rantaian janji yang panjang boleh menghalang UI daripada melukis. Timbunan panggilan (call stack) tidak disekat; halaman tersebut hanya tidak pernah sampai ke langkah perenderaan, yang terasa seperti pembekuan kepada pengguna.
Gelung dipacu libuv Node
Node.js menyerahkan gelungnya kepada libuv, sebuah perpustakaan C yang membahagikan kerja kepada fasa-fasa yang berbeza, setiap satunya dengan baris gilir sendiri:
- Timers – panggilan balik daripada
setTimeoutdansetInterval. - Pending callbacks – panggilan balik I/O tertangguh yang telah selesai pada peringkat OS.
- Poll – mengambil acara I/O baharu (bacaan fail, data rangkaian).
- Check – menjalankan panggilan balik
setImmediate. - Close callbacks – dicetuskan apabila soket atau pemegang (handle) ditutup.
Dua binaan berada di luar urutan fasa ini:
process.nextTick– dijalankan sebelum baris gilir mikrotugasan, sejurus selepas operasi semasa selesai.
Perangkap: kebuluran I/O (I/O starvation)
Jika sesuatu fungsi menjadualkan process.nextTick berulang kali tanpa menyerah (yielding), Node tidak akan maju melampaui langkah "next-tick". Permintaan rangkaian, bacaan fail, dan pemasa (timers) akan terbiar, menyebabkan lonjakan kependaman (latency) di bahagian pelayan atau kegagalan sistem sepenuhnya.
setImmediate lawan setTimeout dalam praktis
Kedua-duanya menjadualkan panggilan balik untuk iterasi seterusnya, tetapi urutan relatifnya bergantung pada di mana ia dipanggil:
- Kod peringkat tinggi (Top-level code) – urutan tidak dijamin; ia bergantung pada seberapa cepat proses bermula.
- Di dalam panggilan balik I/O – urutannya adalah deterministik:
setImmediatedijalankan sebelumsetTimeout(fn, 0). Selepas fasa Poll selesai, libuv beralih ke fasa Check (di manasetImmediateberada) sebelum ia memasuki semula fasa Timers untuk masa tamat (timeout) sifar-kelewatan.
Kehalusan ini penting apabila anda bergantung pada turutan yang tepat, seperti membersihkan sumber sejurus selepas bacaan selesai.
Perbezaan utama sepintas lalu
- Matlamat: Pelayar mengutamakan kemas kini visual; Node mengutamakan kesediaan I/O.
- Cangkuk perenderaan:
requestAnimationFrame(pelayar sahaja). - Cangkuk khusus fasa:
setImmediate(Node sahaja, dicetuskan dalam fasa Check). - Baris gilir keutamaan tinggi:
process.nextTick(Node sahaja, dijalankan sebelum mikrotugasan). - Risiko kebuluran: Rantaian janji yang panjang dalam pelayar;
process.nextTicktanpa had dalam Node.
Apa yang perlu diperhatikan seterusnya
Jika anda menyelenggara kod sumber yang berjalan dalam kedua-dua persekitaran (contohnya, perpustakaan isomorfik), audit mana-mana tempat di mana anda:
- Merantaikan banyak janji tanpa menyerah kepada gelung acara. Masukkan
await new Promise(r => setTimeout(r, 0))atau gunakanrequestIdleCallbackdalam pelayar untuk memberi peluang kepada perender. - Menggunakan
process.nextTickuntuk kerja yang boleh ditangguhkan. UtamakansetImmediateatau janji biasa apabila anda tidak memerlukan kecemasan "next-tick". - Menganggap
setTimeout(fn, 0)dansetImmediateboleh ditukar ganti. Uji urutan di dalam panggilan balik I/O jika turutan itu penting.
Rumusan
Event loop ialah penjadual khusus hos, bukannya ciri JavaScript sejagat. Pelayar menyatukan rendering ke dalam loop; Node mengasingkan I/O ke dalam fasa libuv. Penyalahgunaan mekanisme keutamaan—microtasks dalam pelayar, process.nextTick dalam Node—boleh menyebabkan bahagian sistem yang dibina untuk setiap persekitaran tersebut mengalami kebuluran sumber. Selaraskan corak async anda dengan model loop hos, dan anda akan dapat mengelakkan halaman yang membeku serta pelayan yang tersekat.
