உங்கள் வலைப்பக்கத்தை இயக்கும் JavaScript event loop, Node.js சேவையகத்தை (server) இயக்கும் event loop-விலிருந்து முற்றிலும் மாறுபட்டது. நீங்கள் கவனமாக இல்லையென்றால், இந்த வேறுபாடு UI-ஐ முடக்கலாம் (freeze) அல்லது I/O செயல்பாடுகளைத் தடை செய்யலாம். இந்த இரண்டு சூழல்களிலும் இயங்கும் async குறியீடுகளை (code) எழுதுபவர்கள், இவை இரண்டும் எங்கு வேறுபடுகின்றன என்பதைத் தெரிந்து கொள்வது அவசியமாகும்.
இந்த வேறுபாடு ஏன் முக்கியமானது
Event loop என்பது ECMAScript விதிகளால் (spec) வரையறுக்கப்படவில்லை; அது host-இல் உள்ளது. உலாவிகள் (Browsers) ஒரு பக்கத்தை render செய்யும் போது, அது பயனருக்குத் தடையின்றி இயங்குவதை உறுதி செய்ய வேண்டும், ஆனால் Node.js என்பது non-blocking I/O-வைச் சுற்றி கட்டமைக்கப்பட்டுள்ளது. ஒரு host-இல் வேலை செய்யும் முறைகளை மற்றொன்றில் கலப்பது, மீண்டும் உருவாக்க முடியாத (hard to reproduce) பிழைகளை ஏற்படுத்தலாம்: ஒரு நீண்ட promise சங்கிலி உலாவியின் repaint செயல்பாட்டைத் தாமதப்படுத்தலாம், அதே சமயம் கட்டுப்பாடற்ற process.nextTick loop, Node-இன் I/O நிலைகளை (phases) அடைய விடாமல் தடுக்கலாம்.
உலாவியின் வரிசைமுறை அடிப்படையிலான loop
ஒரு உலாவியில், loop என்பது task execution, microtask draining மற்றும் rendering ஆகியவற்றை ஒன்றிணைத்து ஒரு சுழற்சியை (cycle) இயக்குகிறது:
- ஒரு macrotask-ஐ இயக்குகிறது (ஒரு click handler,
setTimeoutபோன்றவை). - அனைத்து microtasks-களையும் முடிக்கிறது (promises,
queueMicrotask). - ஒரு frame வர வேண்டியிருந்தால், 60 fps இலக்கை அடைய paint மற்றும் composite செய்கிறது.
- மீண்டும் செய்கிறது.
இரண்டு API-கள் டெவலப்பர்களுக்கு இந்தச் சுழற்சியில் நேரடித் தொடர்பை வழங்குகின்றன:
requestAnimationFrame– உலாவியின் paint செய்வதற்குச் சற்று முன் அழைக்கப்படுகிறது. இது அனிமேஷன் வேலைகளுக்குச் சரியான இடமாகும், ஏனெனில் இதன் callback தற்போதைய microtasks முடிந்த பிறகு, ஆனால் அடுத்த frame தொடங்குவதற்கு முன் இயங்கும்.requestIdleCallback– உலாவியில் அதிக முன்னுரிமை கொண்ட வேலைகள் இல்லாதபோது அழைக்கப்படுகிறது. இது analytics அல்லது தரவுகளை முன்கூட்டியே ஏற்றுதல் (pre-loading data) போன்ற குறைந்த தாக்கமுள்ள வேலைகளுக்குப் பயனுள்ளதாக இருக்கும்.
சிக்கல்: microtask starvation
உலாவியானது render செய்வதற்கு முன் microtask queue-வை காலியாக்குவதால், ஒரு நீண்ட promise சங்கிலி UI-ஐ render செய்ய விடாமல் தடுக்கலாம். මෙහි call stack முடங்குவதில்லை; பக்கம் render நிலையை அடையவே இல்லை, இது பயனருக்குத் திரை முடங்கியது போன்ற உணர்வைத் தரும்.
Node-இன் libuv-ஆல் இயக்கப்படும் loop
Node.js தனது loop-ஐ libuv-இடம் ஒப்படைக்கிறது. இது ஒரு C library ஆகும், இது வேலைகளைத் தனித்தனி நிலைகளாகப் (phases) பிரிக்கிறது, ஒவ்வொன்றும் ஒரு தனிப்பட்ட queue-வைக் கொண்டிருக்கும்:
- Timers –
setTimeoutமற்றும்setInterval-லிருந்து வரும் callbacks. - Pending callbacks – OS மட்டத்தில் ஏற்கனவே முடிவடைந்த தாமதப்படுத்தப்பட்ட I/O callbacks.
- Poll – புதிய I/O நிகழ்வுகளைப் (file reads, network data) பெறுகிறது.
- Check –
setImmediatecallbacks-ஐ இயக்குகிறது. - Close callbacks – ஒரு socket அல்லது handle மூடப்படும்போது இயங்குகிறது.
இந்த நிலைகளின் வரிசைக்கு வெளியே இரண்டு கட்டமைப்புகள் உள்ளன:
process.nextTick– தற்போதைய செயல்பாடு முடிந்தவுடன், microtask queue-விற்கு முன் உடனடியாக இயங்குகிறது.
சிக்கல்: I/O starvation
ஒரு function இடைவிடாமல் process.nextTick-ஐத் திட்டமிட்டுக் கொண்டே இருந்தால், Node அடுத்த நிலைக்குச் செல்லாது. இதனால் Network requests, file reads மற்றும் timers ஆகியவை காத்திருக்க வேண்டிய நிலைக்குத் தள்ளப்பட்டு, server-side latency அதிகரிப்பதற்கோ அல்லது சர்வர் முழுமையாக முடங்குவதற்கோ காரணமாகலாம்.
நடைமுறையில் setImmediate vs. setTimeout
இரண்டும் அடுத்த சுழற்சிக்காக callbacks-களைத் திட்டமிடுகின்றன, ஆனால் அவற்றின் வரிசை அவை எங்கு அழைக்கப்படுகின்றன என்பதைப் பொறுத்தது:
- Top-level code – வரிசை உறுதி செய்யப்படவில்லை; இது process எவ்வளவு விரைவாகத் தொடங்குகிறது என்பதைப் பொறுத்தது.
- Inside an I/O callback – வரிசை நிலையானது (deterministic):
setImmediate,setTimeout(fn, 0)-க்கு முன்னால் இயங்கும். Poll phase முடிந்ததும், libuv அடுத்த Timers phase-க்குச் செல்வதற்கு முன் Check phase-க்கு (இங்குதான்setImmediateஇருக்கும்) செல்லும்.
ஒரு resource-ஐப் பயன்படுத்திய பிறகு உடனடியாக அதைச் சுத்தம் செய்வது போன்ற துல்லியமான வரிசைமுறை தேவைப்படும் இடங்களில் இந்த நுணுக்கம் முக்கியமானது.
முக்கிய வேறுபாடுகள் ஒரு பார்வையில்
- இலக்கு (Goal): உலாவிகள் காட்சி மாற்றங்களுக்கு (visual updates) முன்னுரிமை அளிக்கின்றன; Node I/O தயார்நிலைக்கு முன்னுரிமை அளிக்கிறது.
- Rendering hook:
requestAnimationFrame(உலாவியில் மட்டும்). - Phase-specific hook:
setImmediate(Node-இல் மட்டும், Check phase-இல் இயங்குகிறது). - High-priority queue:
process.nextTick(Node-இல் மட்டும், microtasks-க்கு முன் இயங்குகிறது). - Starvation அபாயம்: உலாவிகளில் நீண்ட promise சங்கிலிகள்; Node-இல் கட்டுப்பாடற்ற
process.nextTick.
அடுத்து கவனிக்க வேண்டியவை
நீங்கள் இரண்டு சூழல்களிலும் இயங்கும் ஒரு codebase-ஐப் பராமரித்தால் (உதாரணமாக, isomorphic libraries), பின்வரும் இடங்களில் கவனமாக இருக்கவும்:
- event loop-க்கு இடைவெளி விடாமல் பல promises-களைச் சங்கிலித் தொடராக இணைக்காதீர்கள்.
await new Promise(r => setTimeout(r, 0))என்பதைப் பயன்படுத்தவும் அல்லது உலாவியில்requestIdleCallback-ஐப் பயன்படுத்தி renderer-க்கு ஒரு வாய்ப்பு வழங்கவும். - தள்ளிப்போடக்கூடிய வேலைகளுக்கு
process.nextTick-ஐப் பயன்படுத்தாதீர்கள். "next-tick" அவசரம் தேவையில்லை என்றால்setImmediateஅல்லது சாதாரண promise-ஐப் பயன்படுத்தவும். setTimeout(fn, 0)மற்றும்setImmediateஆகிய இரண்டும் ஒன்றுக்கொன்று மாற்றாகப் பயன்படுத்தக்கூடியவை என்று நினைக்காதீர்கள். வரிசை முக்கியம் என்றால் I/O callbacks-க்குள் அவற்றின் வரிசையைச் சோதித்துப் பார்க்கவும்.
முக்கியக் குறிப்பு
Event loop என்பது ஒரு host-குறிப்பிட்ட scheduler ஆகும், இது ஒரு பொதுவான JavaScript அம்சம் அல்ல. Browsers, rendering-ஐ loop-உடன் ஒருங்கிணைக்கின்றன; Node, I/O-வை libuv நிலைகளாகப் பிரிக்கிறது. முன்னுரிமை வழிமுறைகளைத் தவறாகப் பயன்படுத்துவது—browser-இல் microtasks, Node-இல் process.nextTick—ஒவ்வொரு சூழலும் சேவை செய்ய உருவாக்கப்பட்ட அமைப்பின் ஒரு பகுதியைச் செயலிழக்கச் செய்யலாம். உங்கள் async முறைகளை host-இன் loop மாதிரியுடன் ஒத்துப்போகச் செய்வதன் மூலம், உறைந்துபோன பக்கங்கள் மற்றும் முடங்கிய சர்வர்கள் ஆகிய இரண்டையும் நீங்கள் தவிர்க்கலாம்.
