உங்கள் வலைப்பக்கத்தை இயக்கும் 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) இயக்குகிறது:

  1. ஒரு macrotask-ஐ இயக்குகிறது (ஒரு click handler, setTimeout போன்றவை).
  2. அனைத்து microtasks-களையும் முடிக்கிறது (promises, queueMicrotask).
  3. ஒரு frame வர வேண்டியிருந்தால், 60 fps இலக்கை அடைய paint மற்றும் composite செய்கிறது.
  4. மீண்டும் செய்கிறது.

இரண்டு 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-வைக் கொண்டிருக்கும்:

  1. TimerssetTimeout மற்றும் setInterval-லிருந்து வரும் callbacks.
  2. Pending callbacks – OS மட்டத்தில் ஏற்கனவே முடிவடைந்த தாமதப்படுத்தப்பட்ட I/O callbacks.
  3. Poll – புதிய I/O நிகழ்வுகளைப் (file reads, network data) பெறுகிறது.
  4. ChecksetImmediate callbacks-ஐ இயக்குகிறது.
  5. 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 மாதிரியுடன் ஒத்துப்போகச் செய்வதன் மூலம், உறைந்துபோன பக்கங்கள் மற்றும் முடங்கிய சர்வர்கள் ஆகிய இரண்டையும் நீங்கள் தவிர்க்கலாம்.