JavaScript इवेंट लूप जो आपके वेब पेज को चलाता है, वह उस इवेंट लूप से बहुत अलग व्यवहार करता है जो Node.js सर्वर को संचालित करता है, और यदि आप सावधान नहीं हैं, तो यह अंतर UI को फ्रीज कर सकता है या I/O को बाधित कर सकता है। जो कोई भी ऐसा async कोड लिखता है जो दोनों वातावरणों (environments) में चलता है, उसके लिए यह जानना आवश्यक है कि दोनों कहाँ अलग होते हैं।

यह अंतर क्यों महत्वपूर्ण है

इवेंट लूप ECMAScript spec द्वारा परिभाषित नहीं है; यह होस्ट (host) में रहता है। ब्राउज़रों को फ्रेम रेंडर करते समय पेज को रिस्पॉन्सिव बनाए रखना होता है, जबकि Node.js नॉन-ब्लॉकिंग I/O के इर्द-गिर्द बनाया गया है। एक होस्ट में काम करने वाले पैटर्न को दूसरे के साथ मिलाने से ऐसे बग पैदा हो सकते हैं जिन्हें दोहराना कठिन होता है: प्रॉमिस (promises) की एक लंबी श्रृंखला ब्राउज़र के रिपेंट (repaint) को रोक सकती है, जबकि एक अनियंत्रित process.nextTick लूप Node को उसके I/O चरणों तक पहुँचने से रोक सकता है।

ब्राउज़र का टर्न-आधारित लूप

ब्राउज़र में, लूप एक सिंगल साइकिल चलाता है जो टास्क निष्पादन (task execution), माइक्रोटैस्क ड्रेनिंग (microtask draining), और रेंडरिंग को आपस में जोड़ता है:

  1. एक मैक्रोटैस्क चलाएं (एक क्लिक हैंडलर, एक setTimeout, आदि)।
  2. सभी माइक्रोटैस्क ड्रेन करें (promises, queueMicrotask)।
  3. यदि कोई फ्रेम ड्यू है, तो लक्ष्य 60 fps तक पहुँचने के लिए पेंट और कंपोजिट करें।
  4. दोहराएं।

दो API डेवलपर्स को इस चक्र में स्पष्ट हुक (hooks) प्रदान करते हैं:

  • requestAnimationFrame – ब्राउज़र द्वारा पेंट करने से ठीक पहले कॉल किया जाता है। यह एनिमेशन कार्य के लिए सही जगह है क्योंकि कॉलबैक वर्तमान माइक्रोटैस्क के बाद लेकिन अगले फ्रेम से पहले चलता है।
  • requestIdleCallback – तब कॉल किया जाता है जब ब्राउज़र के पास कोई हाई-प्रायोरिटी काम नहीं होता है। यह एनालिटिक्स या डेटा प्री-लोडिंग जैसे कम प्रभाव वाले कार्यों के लिए उपयोगी है।

सावधानी: माइक्रोटैस्क स्टारवेशन (microtask starvation)

चूंकि ब्राउज़र रेंडर करने से पहले माइक्रोटैस्क क्यू को खाली कर देता है, इसलिए प्रॉमिस की एक लंबी श्रृंखला UI को कभी पेंट करने नहीं दे सकती। कॉल स्टैक ब्लॉक नहीं होता है; पेज बस कभी रेंडर स्टेप तक नहीं पहुँच पाता, जो उपयोगकर्ता को फ्रीज जैसा महसूस होता है।

Node का libuv-संचालित लूप

Node.js अपने लूप को libuv को सौंप देता है, जो एक C लाइब्रेरी है और काम को अलग-अलग चरणों में विभाजित करती है, जिनमें से प्रत्येक की अपनी क्यू (queue) होती है:

  1. TimerssetTimeout और setInterval से कॉलबैक।
  2. Pending callbacks – विलंबित (deferred) I/O कॉलबैक जो OS स्तर पर पहले ही पूरे हो चुके हैं।
  3. Poll – नए I/O इवेंट्स (फ़ाइल रीड, नेटवर्क डेटा) प्राप्त करता है।
  4. ChecksetImmediate कॉलबैक चलाता है।
  5. Close callbacks – जब कोई सॉकेट या हैंडल बंद होता है तो फायर होता है।

दो कंस्ट्रक्ट्स इस चरण क्रम (phase order) के बाहर स्थित हैं:

  • process.nextTick – वर्तमान ऑपरेशन समाप्त होने के तुरंत बाद, माइक्रोटैस्क क्यू से पहले चलता है।

सावधानी: I/O स्टारवेशन (I/O starvation)

यदि कोई फंक्शन बिना नियंत्रण छोड़े (without yielding) बार-बार process.nextTick शेड्यूल करता है, तो Node कभी भी "next-tick" चरण से आगे नहीं बढ़ पाता। नेटवर्क अनुरोध, फ़ाइल रीड और टाइमर खाली बैठे रहते हैं, जिससे सर्वर-साइड लेटेंसी स्पाइक्स या पूरी तरह से हैंग होने की स्थिति पैदा हो सकती है।

व्यवहार में setImmediate बनाम setTimeout

दोनों अगले इटरेशन के लिए कॉलबैक शेड्यूल करते हैं, लेकिन उनका सापेक्ष क्रम इस बात पर निर्भर करता है कि उन्हें कहाँ कॉल किया गया है:

  • Top-level कोड – क्रम की गारंटी नहीं है; यह इस बात पर निर्भर करता है कि प्रोसेस कितनी जल्दी शुरू होता है।
  • I/O callback के अंदर – क्रम निश्चित (deterministic) है: setImmediate, setTimeout(fn, 0) से पहले चलता है। Poll चरण समाप्त होने के बाद, libuv ज़ीरो-डिलीवरी टाइमआउट के लिए Timers चरण में फिर से प्रवेश करने से पहले Check चरण (जहाँ setImmediate रहता है) में चला जाता है।

यह सूक्ष्मता तब मायने रखती है जब आप सटीक अनुक्रमण (precise sequencing) पर भरोसा करते हैं, जैसे कि रीड पूरा होने के तुरंत बाद किसी रिसोर्स को क्लीन अप करना।

एक नज़र में मुख्य अंतर

  • लक्ष्य (Goal): ब्राउज़र विजुअल अपडेट्स को प्राथमिकता देते हैं; Node I/O तत्परता (readiness) को प्राथमिकता देता है।
  • रेंडरिंग हुक: requestAnimationFrame (केवल ब्राउज़र)।
  • फेज-विशिष्ट हुक: setImmediate (केवल Node, Check चरण में फायर होता है)।
  • हाई-प्रायोरिटी क्यू: process.nextTick (केवल Node, माइक्रोटैस्क से पहले चलता है)।
  • स्टारवेशन का जोखिम: ब्राउज़रों में लंबी प्रॉमिस श्रृंखला; Node में अनबाउंडेड process.nextTick

आगे क्या देखें

यदि आप ऐसे कोडबेस का रखरखाव करते हैं जो दोनों वातावरणों में चलता है (जैसे, आइसोमॉर्फिक लाइब्रेरी), तो किसी भी ऐसी जगह का ऑडिट करें जहाँ आप:

  • इवेंट लूप को yield किए बिना कई प्रॉमिस को चेन करते हैं। रेंडरर को मौका देने के लिए await new Promise(r => setTimeout(r, 0)) डालें या ब्राउज़र में requestIdleCallback का उपयोग करें।
  • ऐसे काम के लिए process.nextTick का उपयोग करते हैं जिसे टाला (defer) जा सकता है। जब आपको "next-tick" तात्कालिकता की आवश्यकता न हो, तो setImmediate या एक नियमित प्रॉमिस को प्राथमिकता दें।
  • यह मान लेते हैं कि setTimeout(fn, 0) और setImmediate एक-दूसरे के बदले में उपयोग किए जा सकते हैं। यदि अनुक्रम (sequence) मायने रखता है, तो I/O कॉलबैक के अंदर ऑर्डर का परीक्षण करें।

मुख्य बातें

event loop एक होस्ट-विशिष्ट शेड्यूलर है, न कि कोई सार्वभौमिक JavaScript विशेषता। ब्राउज़र लूप में रेंडरिंग को शामिल करते हैं; Node, I/O को libuv चरणों में अलग करता है। प्राथमिकता तंत्र का गलत उपयोग—जैसे ब्राउज़र में microtasks, या Node में process.nextTick—सिस्टम के उस हिस्से को बाधित कर सकता है जिसे सेवा देने के लिए प्रत्येक वातावरण बनाया गया है। अपने async पैटर्न को होस्ट के लूप मॉडल के साथ संरेखित करें, और आप फ्रीज़ हुए पेजों और ब्लॉक हुए सर्वर, दोनों से बच सकेंगे।