Kodokon kodokon.com

Node इवेंट लूप: फेज़ और उनके जाल

libuv के फेज़, setImmediate और setTimeout के बीच का असली अंतर, और अवरुद्ध इवेंट लूप का पता कैसे लगाएँ, यह जानें।

10 मिनट · 3 प्रश्न

इस पाठ को Kodokon में खोलें

Node.js अपने इवेंट लूप को libuv को सौंपता है, जो इसे अलग-अलग फेज़ में व्यवस्थित करता है: timers (setTimeout, setInterval), pending callbacks, idle/prepare (आंतरिक), poll (I/O: सॉकेट, फ़ाइलें), check (setImmediate), और close callbacks। हर टिक पर, लूप इन फेज़ से इसी क्रम में गुज़रता है। एक ऐसी बारीकी जिसे कम ही डेवलपर जानते हैं: हर कॉलबैक के बीच, Node पहले process.nextTick कतार को खाली करता है, फिर प्रॉमिस माइक्रोटास्क को - और Node 11 से ऐसा ही है, यानी हर कॉलबैक के बाद, न कि केवल फेज़ के बीच।

JAVASCRIPT
import { readFile } from "node:fs";

setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));

readFile(new URL(import.meta.url), () => {
  setTimeout(() => console.log("io timeout"), 0);
  setImmediate(() => console.log("io immediate"));
});
यह क्रम केवल किसी I/O कॉलबैक के अंदर ही सुनिश्चित होता है।

जब स्क्रिप्ट शुरू होती है, तब setTimeout(fn, 0) और setImmediate(fn) के बीच का क्रम अनिश्चित होता है: setTimeout(fn, 0) वास्तव में 1 ms तक सीमित कर दिया जाता है, और यदि लूप एक मिलीसेकंड से कम में timers फेज़ तक पहुँच जाता है, तो टाइमर अभी योग्य नहीं होता। लेकिन किसी I/O कॉलबैक के अंदर, आप poll फेज़ में होते हैं: check फेज़ (setImmediate) हमेशा timers फेज़ पर लौटने से पहले आता है। इसलिए io immediate लगातार io timeout से पहले प्रिंट होता है।

JAVASCRIPT
setTimeout(() => {
  process.nextTick(() => console.log("nextTick"));
  Promise.resolve().then(() => console.log("promise"));
  queueMicrotask(() => console.log("microtask"));
  console.log("sync");
}, 0);
// sync, nextTick, promise, microtask
किसी कॉलबैक के अंदर, nextTick माइक्रोटास्क से पहले चलता है।

एक सूक्ष्म जाल: किसी ES मॉड्यूल के शीर्ष स्तर पर, यह क्रम उलट जाता है - प्रॉमिस माइक्रोटास्क process.nextTick से पहले चलते हैं। इसका कारण: किसी ES मॉड्यूल का मूल्यांकन स्वयं एक प्रॉमिस जॉब के रूप में निष्पादित होता है, इसलिए जब आपका सिंक्रोनस कोड समाप्त होता है, तब तक माइक्रोटास्क कतार पहले से ही खाली की जा रही होती है। CommonJS में, nextTick शीर्ष स्तर पर भी अपनी प्राथमिकता बनाए रखता है। इस तरह के क्रम को हमेशा उसी मॉड्यूल फ़ॉर्मेट में सत्यापित करें जिसे आप वास्तव में तैनात करते हैं।

इवेंट लूप एक ही थ्रेड पर चलता है: कोई भी लंबी सिंक्रोनस गणना - किसी बड़े पेलोड का JSON.parse, एक विनाशकारी रेगुलर एक्सप्रेशन, सिंक्रोनस एन्क्रिप्शन - चल रही सभी रिक्वेस्ट को जमा देती है। प्रोडक्शन में, monitorEventLoopDelay से लूप की लेटेंसी मापें: कुछ दसियों मिलीसेकंड से ऊपर का p99 किसी रुकावट का संकेत देता है। नीचे दिया गया कोड जानबूझकर एक रुकावट पैदा करता है और फिर उसे मापता है।

JAVASCRIPT
import { monitorEventLoopDelay }
  from "node:perf_hooks";

const h = monitorEventLoopDelay({ resolution: 10 });
h.enable();

const end = Date.now() + 200;
while (Date.now() < end) {}

setTimeout(() => {
  h.disable();
  const ms = h.max / 1e6;
  console.log(`max delay: ${ms.toFixed(1)} ms`);
}, 300);
हिस्टोग्राम 200 ms की रुकावट को उजागर करता है।

ज्ञान जांच

सुनिश्चित करें कि आपको इस पाठ के मुख्य बिंदु याद हैं।

  1. किसी I/O कॉलबैक के अंदर (उदाहरण के लिए fs.readFile), आप setTimeout(fn, 0) और setImmediate(fn) शेड्यूल करते हैं। कौन सा पहले चलता है?
    • setTimeout, हमेशा
    • setImmediate, हमेशा
    • क्रम यादृच्छिक होता है, जैसा कि स्क्रिप्ट के शुरू होने पर होता है
  2. किसी कॉलबैक के बाद, Node प्राथमिकता कतारों को किस क्रम में खाली करता है?
    • प्रॉमिस माइक्रोटास्क पहले जाते हैं
    • दोनों कतारें कड़ाई से बारी-बारी से खाली की जाती हैं
    • पहले process.nextTick कतार खाली की जाती है, फिर माइक्रोटास्क
  3. कौन सा नेटिव टूल प्रोडक्शन में इवेंट लूप की रुकावटों को सटीक रूप से मापता है?
    • हर रिक्वेस्ट के आसपास console.time
    • node:perf_hooks से monitorEventLoopDelay
    • किसी setInterval के अंदर कॉल किया गया process.memoryUsage
    • node:trace_events मॉड्यूल