प्रतिनिधि इंटरव्यू विषय

फ़्रंटएंड इंटरव्यू: JavaScript इवेंट लूप कैसे काम करता है?

फ्रंटएंडकठिन
Offer.cc संपादकीय टीमप्रकाशित अपडेट किया गया

प्रश्न

एक क्लिक हैंडलर Promise रिएक्शन, queueMicrotask, setTimeout, और रिकर्सिव माइक्रोटास्क की एक बड़ी शृंखला शेड्यूल करता है। लॉग ऑर्डर का पूर्वानुमान लगाएं, बताएं कि टाइमर, इनपुट और पेंटिंग में देरी क्यों होती है, और पेज को रिस्पॉन्सिव रखने के लिए गणना को फिर से डिज़ाइन करें।

प्रश्न और इसका उपयोग कब करें

आप डेटा डैशबोर्ड में एक क्लिक को हैंडल कर रहे हैं। इस कोड के आउटपुट का पूर्वानुमान लगाने से शुरुआत करें:

javascript
console.log("A");

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

Promise.resolve().then(() => {
  console.log("promise");
  queueMicrotask(() => console.log("nested"));
});

queueMicrotask(() => console.log("microtask"));

console.log("B");

प्रत्येक चरण की व्याख्या करें, फिर प्रदर्शन से जुड़ी इस समस्या का विश्लेषण करें:

javascript
let remaining = 100_000;

function continueInMicrotask() {
  remaining -= 1;
  if (remaining > 0) {
    queueMicrotask(continueInMicrotask);
  }
}

queueMicrotask(continueInMicrotask);
setTimeout(() => console.log("timer can run"), 0);
requestAnimationFrame(() => console.log("frame can render"));

बताएं कि टाइमर, बाद के क्लिक और पेंटिंग में देरी क्यों होती है, फिर बैच गणना को फिर से डिज़ाइन करें ताकि पेज रिस्पॉन्सिव बना रहे। यह प्रश्न ब्राउज़र मेन थ्रेड पर JavaScript से संबंधित है। एक वर्कर का अपना इवेंट लूप होता है, और Node.js का एक फ़ेज़ मॉडल होता है जिसे सीधे ब्राउज़र के उत्तर में कॉपी नहीं किया जाना चाहिए।

यह मिड-लेवल और सीनियर फ़्रंटएंड, वेब-परफ़ॉर्मेंस और फ़ुल-स्टैक इंटरव्यू के लिए उपयोगी है। एक मजबूत उत्तर शेड्यूलिंग मॉडल को अवलोकनीय उपयोगकर्ता अनुभव से जोड़ता है: काम के अंततः पूरा होने का यह मतलब नहीं है कि काम चलने के दौरान पेज इंटरैक्टिव बना रहा।

इंटरव्यूअर क्या मूल्यांकन कर रहा है

पहला संकेत एक सटीक मॉडल है। प्रारंभिक स्क्रिप्ट निष्पादन, क्लिक कॉलबैक और समाप्त हो चुके टाइमर कॉलबैक टास्क के रूप में चलते हैं। Promise रिएक्शन, queueMicrotask() कॉलबैक, और MutationObserver कॉलबैक माइक्रोटास्क का उपयोग करते हैं। किसी टास्क के समाप्त होने के बाद, इवेंट लूप एक माइक्रोटास्क चेकपॉइंट निष्पादित करता है और तब तक माइक्रोटास्क को प्रोसेस करता रहता है जब तक कि कतार खाली न हो जाए।

दूसरा संकेत यह है कि क्या उम्मीदवार ब्राउज़र को केवल एक स्थायी "मैक्रोटास्क कतार" के रूप में वर्णित करने से बचता है। HTML मानक विभिन्न टास्क स्रोतों को विभिन्न टास्क कतारों से जुड़े होने की अनुमति देता है। एक ब्राउज़र एक ही टास्क स्रोत के भीतर क्रम को बनाए रखते हुए चलने योग्य कतारों में से एक कार्यान्वयन-परिभाषित (implementation-defined) विकल्प चुन सकता है। "मैक्रोटास्क" बातचीत की बोलचाल हो सकती है, लेकिन टास्क अधिक सटीक मानक शब्द है।

तीसरा संकेत एक सही रेंडरिंग सीमा है। माइक्रोटास्क चेकपॉइंट समाप्त होने के बाद ही ब्राउज़र अन्य टास्क या रेंडरिंग अपडेट पर आगे बढ़ सकता है, और प्रत्येक टास्क के बाद रेंडरिंग की गारंटी नहीं होती है। यदि कोड कतार खाली होने से पहले एक और माइक्रोटास्क जोड़ता रहता है, तो इनपुट इवेंट, टाइमर और रेंडरिंग के अवसर सभी बाधित (starved) हो सकते हैं।

चौथा संकेत सही शेड्यूलिंग प्रिमिटिव का चयन करना है। await Promise.resolve() फ़ंक्शन को केवल एक माइक्रोटास्क में फिर से शुरू करता है, इसलिए यह बाद के किसी टास्क या पेंट को पहले चलने की अनुमति नहीं देता है। एक वास्तविक मेन-थ्रेड यील्ड भविष्य के टास्क में निरंतरता को शेड्यूल करता है, जैसे कि जहां समर्थित हो वहां scheduler.yield() या एक setTimeout() फ़ॉलबैक। CPU का काम जिसे सुरक्षित रूप से टुकड़ों में विभाजित नहीं किया जा सकता, वह वर्कर में होना चाहिए।

उत्तर देने से पहले स्पष्ट करने योग्य प्रश्न

  • किन ब्राउज़रों का समर्थन किया जाना चाहिए? scheduler.yield() हर व्यापक रूप से उपयोग किए जाने वाले ब्राउज़र में उपलब्ध नहीं है, इसलिए व्यापक समर्थन के लिए फ़ीचर डिटेक्शन और एक फ़ॉलबैक की आवश्यकता होती है।
  • क्या काम को रिकॉर्ड्स के बीच विभाजित किया जा सकता है, या एक कॉल लंबे समय तक ब्लॉक कर सकती है? एक विभाज्य लूप बैचों के बीच यील्ड कर सकता है। यदि एक रिकॉर्ड अपने आप में महंगा है, तो मेन थ्रेड पर बैचिंग अभी भी एक लंबे ब्लॉक की अनुमति देती है; वर्कर का उपयोग करें या एल्गोरिदम बदलें।
  • क्या प्रत्येक बैच के बाद प्रगति को पेंट किया जाना चाहिए, या केवल अंतिम परिणाम की आवश्यकता है? दृश्यमान प्रगति के लिए स्थिति को अपडेट करने के बाद मेन-थ्रेड यील्ड की आवश्यकता होती है। केवल अंतिम परिणाम बार-बार होने वाली DOM, लेआउट और पेंट लागत से बच सकता है।
  • क्या परिणामों को सख्त क्रम में कमिट किया जाना चाहिए? वर्कर समवर्ती रूप से गणना कर सकते हैं, लेकिन आउट-ऑफ-ऑर्डर पूरा होने के लिए अनुक्रम संख्या, एक मर्ज नियम, या एक क्रमित कमिट बफ़र की आवश्यकता होती है।
  • क्या बैकग्राउंड टैब में प्रोसेसिंग जारी रहनी चाहिए? अधिकांश बैकग्राउंड टैब में requestAnimationFrame() को रोक दिया जाता है, इसलिए यह अनिवार्य बैकग्राउंड प्रगति के लिए एक खराब सामान्य शेड्यूलर है।
  • रिस्पॉन्सिवनेस को कैसे स्वीकार किया जाएगा? इंटरैक्शन लेटेंसी, प्रति-बैच बजट, कुल थ्रूपुट, और रद्दीकरण व्यवहार पर सहमति बनाएं ताकि "फ़्रीज़ नहीं होता" परीक्षण योग्य बन सके।

30-सेकंड उत्तर रूपरेखा

"ब्राउज़र एक चलने योग्य टास्क का चयन करता है, उसे चलाता है, और फिर एक माइक्रोटास्क चेकपॉइंट निष्पादित करता है जो माइक्रोटास्क कतार को खाली करता है। उसके बाद ही यह रेंडरिंग या किसी अन्य टास्क पर जा सकता है। पहला स्निपेट A और B को सिंक्रोनस रूप से लॉग करता है, फिर एन्क्यू क्रम में promise और microtask को। Promise कॉलबैक पहले से प्रतीक्षा कर रहे माइक्रोटास्क के पीछे नेस्टेड रूप से जुड़ जाता है, और timeout सबसे अंत में चलता है। रिकर्सिव माइक्रोटास्क चेकपॉइंट को खुला रखते हैं, जिससे टाइमर, इनपुट और रेंडरिंग बाधित होते हैं। await Promise.resolve() अभी भी एक माइक्रोटास्क है, इसलिए यह एक वास्तविक मेन-थ्रेड यील्ड नहीं है। मैं चंक करने योग्य काम को टाइम-स्लाइस करूंगा और बैचों के बीच फ़ीचर-डिटेक्टेड scheduler.yield() को कॉल करूंगा, जिसमें setTimeout() फ़ॉलबैक के रूप में होगा। यदि एक इकाई अभी भी CPU-भारी है, तो मैं इसे एक वर्कर में स्थानांतरित करूंगा, फिर परफ़ॉर्मेंस रिकॉर्डिंग और वास्तविक इनपुट के साथ इंटरैक्शन लेटेंसी को सत्यापित करूंगा।"

चरण-दर-चरण समाधान

चरण 1: एन्क्यू समय से आउटपुट निकालें

पूरी स्क्रिप्ट वर्तमान में एक टास्क के रूप में चल रही है। सिंक्रोनस स्टेटमेंट इवेंट लूप की प्रतीक्षा नहीं करते हैं, इसलिए पहला आउटपुट है:

text
A
B

setTimeout(..., 0) का अर्थ है कि समय की स्थिति संतुष्ट होने के बाद, इसका कॉलबैक एक भविष्य का टास्क बन सकता है। शून्य वर्तमान स्क्रिप्ट को बाधित नहीं करता है। Promise.resolve().then(...) पहले माइक्रोटास्क के रूप में एक Promise रिएक्शन को कतारबद्ध करता है, और निम्नलिखित queueMicrotask(...) दूसरे माइक्रोटास्क को कतारबद्ध करता है।

जब स्क्रिप्ट टास्क समाप्त होता है, तो माइक्रोटास्क चेकपॉइंट शुरू होता है। पहला माइक्रोटास्क promise लॉग करता है और माइक्रोटास्क कतार के अंत में nested जोड़ता है। स्पष्ट माइक्रोटास्क पहले से ही प्रतीक्षा कर रहा था, इसलिए यह nested से पहले microtask लॉग करता है। एक बार माइक्रोटास्क कतार खाली हो जाने के बाद, टाइमर टास्क को चलने का अवसर मिलता है:

text
A
B
promise
microtask
nested
timeout

प्रत्येक पंक्ति के लिए, रिकॉर्ड करें कि कॉलबैक कब कतारबद्ध होता है और यह किस शेड्यूलिंग श्रेणी में प्रवेश करता है। "माइक्रोटास्क को प्राथमिकता है" पर्याप्त नहीं है यदि प्रतिस्पर्धी कॉलबैक में से एक को अभी तक कतारबद्ध नहीं किया गया है।

चरण 2: टास्क, माइक्रोटास्क, और रेंडरिंग सीमाओं को स्थापित करें

एक पुन: प्रयोज्य सरलीकृत क्रम है:

  1. ब्राउज़र एक रन करने योग्य टास्क कतार से एक टास्क का चयन करता है।
  2. यह उस टास्क को तब तक चलाता है जब तक कि JavaScript कॉल स्टैक खाली न हो जाए।
  3. यह एक माइक्रोटास्क चेकपॉइंट निष्पादित करता है; चेकपॉइंट के दौरान जोड़े गए माइक्रोटास्क को उसी चेकपॉइंट में प्रोसेस किया जाता है।
  4. रेंडरिंग के अवसरों, दस्तावेज़ दृश्यता, और कार्यान्वयन नीति के आधार पर, ब्राउज़र रेंडरिंग को अपडेट कर सकता है।
  5. लूप जारी रहता है और किसी अन्य टास्क को प्रोसेस कर सकता है।

यह दो व्यावहारिक अवलोकनों की व्याख्या करता है। पहला, यदि कोई क्लिक हैंडलर DOM को बदलता है और तुरंत एक बड़ी सिंक्रोनस गणना शुरू करता है, तो उपयोगकर्ता आमतौर पर मध्यवर्ती स्थिति को नहीं देखता है क्योंकि ब्राउज़र को फिर से पेंट का अवसर नहीं मिला है। दूसरा, माइक्रोटास्क संक्षिप्त निरंतरता (consistency) कार्य के लिए उपयुक्त हैं जो अन्य घटनाओं और टाइमर से पहले होना चाहिए, लेकिन असीमित या बड़ी रिकर्सिव गणना के लिए नहीं।

चरण 3: माइक्रोटास्क स्टार्वेशन का निदान करें

दूसरे स्निपेट में पहले माइक्रोटास्क का प्रत्येक निष्पादन एक और माइक्रोटास्क को कतारबद्ध करता है। चेकपॉइंट तब तक समाप्त नहीं हो सकता जब तक कि उसकी कतार खाली न हो जाए, इसलिए टाइमर टास्क या अगले क्लिक टास्क के चलने से पहले 100,000 कॉलबैक पूरे हो जाते हैं।

बिना किसी समाप्ति स्थिति के, शेड्यूलिंग मॉडल में माइक्रोटास्क कतार कभी खाली नहीं होती है। एक ब्राउज़र अंततः एक अनरिस्पॉन्सिव-पेज चेतावनी दिखा सकता है, पेज को समाप्त कर सकता है, या एक कार्यान्वयन सुरक्षा लागू कर सकता है, लेकिन एप्लिकेशन की शुद्धता उस पर निर्भर नहीं हो सकती है। requestAnimationFrame() चल रहे JavaScript को प्रीमेम्प्ट नहीं करता है; यह भविष्य के रीपेंट से पहले एक कॉलबैक का अनुरोध करता है। यदि मुख्य थ्रेड प्रासंगिक रेंडरिंग चरणों तक कभी नहीं पहुंचता है, तो कॉलबैक प्रतीक्षा करता है।

यह स्पष्ट यील्ड अभी भी गलत है:

javascript
async function processAll(records) {
  for (const record of records) {
    normalize(record);
    await Promise.resolve();
  }
}

प्रत्येक await निरंतरता एक Promise माइक्रोटास्क के माध्यम से फिर से शुरू होती है। कॉल स्टैक संक्षेप में खाली हो जाता है, लेकिन चेकपॉइंट उन माइक्रोटास्क का उपभोग करना जारी रखता है, इसलिए बाद के इनपुट और टाइमर टास्क अभी भी प्रवेश नहीं कर सकते हैं।

चरण 4: टास्क के पार चंक करने योग्य काम को विभाजित करें

प्रत्येक बैच को एक मापने योग्य समय बजट दें, फिर बैचों के बीच वास्तविक रूप से यील्ड करें:

javascript
function yieldToMain() {
  return globalThis.scheduler?.yield
    ? globalThis.scheduler.yield()
    : new Promise((resolve) => setTimeout(resolve, 0));
}

async function processRecords(records, budgetMs = 5) {
  let index = 0;

  while (index < records.length) {
    const deadline = performance.now() + budgetMs;

    while (index < records.length && performance.now() < deadline) {
      normalize(records[index]);
      index += 1;
    }

    updateProgress(index / records.length);

    if (index < records.length) {
      await yieldToMain();
    }
  }
}

5 मिलीसेकंड का मान इस अभ्यास के लिए एक प्रारंभिक धारणा है, कोई क्रॉस-डिवाइस मानक नहीं है। इसे प्रति-आइटम लागत, इंटरैक्शन लेटेंसी, और थ्रूपुट के आधार पर लक्षित उपकरणों पर ट्यून करें। scheduler.yield() निरंतरता को बाद के प्राथमिकता वाले टास्क के रूप में शेड्यूल करता है, जिससे ब्राउज़र को पहले आवश्यक कार्य को संभालने का मौका मिलता है। चूंकि इसका ब्राउज़र समर्थन अधूरा है, उदाहरण इसे फ़ीचर-डिटेक्ट करता है। setTimeout() फ़ॉलबैक अधिक व्यापक है लेकिन टाइमर क्लैम्पिंग, बैकग्राउंड नीतियों, और अन्य टास्क से प्रतिस्पर्धा से प्रभावित होता है।

एक और सीमा है: बजट की जांच केवल normalize() के कॉलों के बीच की जा सकती है। यदि एक कॉल 80 मिलीसेकंड के लिए ब्लॉक करती है, तो पांच-मिलीसेकंड का बजट मदद नहीं कर सकता है। normalize() को विभाजित करें, एल्गोरिदम को बदलें, या गणना को एक वर्कर में स्थानांतरित करें।

चरण 5: प्रिमिटिव को काम के साथ सुमेलित करें

आवश्यकताविकल्पमुख्य लागत या सीमा
वर्तमान टास्क के बाद लेकिन अन्य घटनाओं से पहले संक्षिप्त क्लीनअप या कंसिस्टेंसी नोटिफ़िकेशन चलाएंqueueMicrotask()रिकर्शन या भारी गणना अन्य कार्यों को बाधित करती है
प्राथमिकता वाली निरंतरता बनाए रखते हुए लंबे मेन-थ्रेड कार्य को विभाजित करेंscheduler.yield()फ़ीचर डिटेक्शन की आवश्यकता है; समर्थन अधूरा है
व्यापक अनुकूलता के साथ भविष्य के टास्क में निरंतरता को स्थानांतरित करेंsetTimeout()टाइमर विलंब और शेड्यूलिंग नियतात्मक (deterministic) नहीं हैं
भविष्य के रीपेंट से पहले एनीमेशन स्थिति को अपडेट करेंrequestAnimationFrame()भारी कॉलबैक कार्य अभी भी उस पेंट को ब्लॉक करता है; बैकग्राउंड टैब आमतौर पर इसे रोक देते हैं
CPU-भारी काम चलाएं जिसे सुरक्षित रूप से विभाजित नहीं किया जा सकताWeb Workerसंदेश, कॉपी, या शेयर्ड-मेमोरी प्रोटोकॉल लागत

requestAnimationFrame() विज़ुअल कार्य को पेंटिंग के साथ संरेखित करता है; यह एक सामान्य बैकग्राउंड-जॉब कतार नहीं है। पेंट से ठीक पहले एक बड़ी गणना को छिपाने के लिए नहीं, बल्कि हल्के विज़ुअल परिवर्तनों को सबमिट करने के लिए इसका उपयोग करें। एक वर्कर पेज के मेन थ्रेड से CPU गणना को हटा देता है, लेकिन यह रद्दीकरण, प्रगति रिपोर्टिंग, परिणाम क्रमबद्धता, या स्थानांतरण लागत को स्वचालित रूप से हल नहीं करता है। उन्हें एक स्पष्ट प्रोटोकॉल की आवश्यकता होती है।

चरण 6: केवल पूर्णता ही नहीं, रिस्पॉन्सिवनेस को भी सत्यापित करें

सत्यापन में कम से कम चार परतें शामिल होनी चाहिए:

  1. क्रम परीक्षण: एक न्यूनतम पेज में सिंक्रोनस कोड, Promise रिएक्शन, queueMicrotask(), और टाइमर लॉग करें और पुष्टि करें कि देखा गया क्रम डेरिवेशन से मेल खाता है।
  2. टाइमलाइन निरीक्षण: ब्राउज़र परफ़ॉर्मेंस टूल में क्लिक, बैच और प्रोग्रेस पेंटिंग रिकॉर्ड करें। लंबे टास्क, निरंतर माइक्रोटास्क, फ़्रेम अंतराल, और इनपुट कॉलबैक वास्तव में कब चलते हैं, इसका निरीक्षण करें।
  3. स्ट्रेस और रद्दीकरण: रिकॉर्ड संख्या बढ़ाएं, CPU को थ्रॉटल करें, प्रोसेसिंग के दौरान क्लिक और स्क्रॉल करें, और ऑपरेशन रद्द करें। सत्यापित करें कि कतारें बिना किसी सीमा के नहीं बढ़ती हैं।
  4. सीमांत वातावरण (Boundary environments): scheduler.yield() के बिना ब्राउज़र में फ़ॉलबैक सत्यापित करें। पेज को बैकग्राउंड टैब में ले जाएं और पुष्टि करें कि व्यावसायिक वर्कफ़्लो गलत तरीके से निरंतर requestAnimationFrame() कॉलबैक पर निर्भर नहीं है।

स्वीकृति के लिए पूर्णता समय और इंटरैक्शन लेटेंसी दोनों की आवश्यकता होती है। बैचिंग से शेड्यूलिंग ओवरहेड जुड़ता है और कुल अवधि थोड़ी बढ़ सकती है; इसका उद्देश्य इनपुट, पेंटिंग और अन्य आवश्यक टास्क के लिए निष्पादन विंडो छोड़ना है। यदि वह थ्रूपुट लागत अस्वीकार्य है, तो एल्गोरिदम को अनुकूलित करें या मेन थ्रेड को फिर से माइक्रोटास्क से भरने के बजाय एक वर्कर का उपयोग करें।

एक मजबूत उत्तर का उदाहरण

"मैं एन्क्यू समय से क्रम निकालूंगा। वर्तमान स्क्रिप्ट एक टास्क है, इसलिए A और B सिंक्रोनस हैं। टाइमर केवल भविष्य के टास्क को शेड्यूल करता है। Promise रिएक्शन स्पष्ट queueMicrotask कॉलबैक से पहले माइक्रोटास्क कतार में प्रवेश करती है, इसलिए चेकपॉइंट पहले promise लॉग करता है। वह कॉलबैक पहले से प्रतीक्षा कर रहे माइक्रोटास्क के पीछे नेस्टेड रूप से जुड़ जाता है। अंतिम क्रम A, B, promise, microtask, nested, timeout है।

एक टास्क के बाद, ब्राउज़र एक माइक्रोटास्क चेकपॉइंट निष्पादित करता है और तब तक जारी रहता है जब तक कि माइक्रोटास्क कतार खाली न हो जाए। दूसरा स्निपेट उस कतार को भरता रहता है, इसलिए चेकपॉइंट लंबे समय तक खुला रहता है। टाइमर और क्लिक बाद के टास्क हैं, और पेंटिंग के लिए मुख्य थ्रेड को रेंडरिंग अवसर तक पहुंचने की आवश्यकता होती है, इसलिए उन सभी में देरी होती है। requestAnimationFrame JavaScript को प्रीमेम्प्ट नहीं कर सकता, और await Promise.resolve केवल एक अन्य माइक्रोटास्क में फिर से शुरू होता है, इसलिए दोनों में से कोई भी स्टार्वेशन को ठीक नहीं करता है।

यदि प्रत्येक रिकॉर्ड तेज़ है, तो मैं एक समय बजट के आधार पर प्रोसेस करूंगा और बैचों के बीच scheduler.yield को कॉल करूंगा। चूंकि यह हर ब्राउज़र में उपलब्ध नहीं है, इसलिए मैं इसे फ़ीचर-डिटेक्ट करूंगा और setTimeout पर फ़ॉलबैक करूंगा। पांच मिलीसेकंड के बजट के साथ शुरुआत करना केवल एक प्रयोग है; मैं इसे इनपुट लेटेंसी और थ्रूपुट के आधार पर लक्षित उपकरणों पर ट्यून करूंगा। यदि एक रिकॉर्ड अपने आप में महंगा है, तो मैं उस ऑपरेशन को विभाजित करूंगा या इसे एक वर्कर में स्थानांतरित करूंगा।

अंत में, मैं एक परफ़ॉर्मेंस टाइमलाइन रिकॉर्ड करूंगा और पुष्टि करूंगा कि कोई निरंतर माइक्रोटास्क वॉटरफॉल नहीं है, प्रगति वास्तव में पेंट हो रही है, प्रोसेसिंग के दौरान क्लिक और स्क्रॉलिंग चलती है, और रद्दीकरण, बैकग्राउंड टैब और कम्पैटिबिलिटी फ़ॉलबैक सही ढंग से व्यवहार करते हैं। यह डिज़ाइन मापने योग्य रिस्पॉन्सिवनेस के बदले में कुछ शेड्यूलिंग ओवरहेड को स्वीकार करता है।"

सामान्य गलतियाँ

  • "sync, microtask, macrotask" का रटना → यह स्पष्ट नहीं कर सकता कि एक नेस्टेड माइक्रोटास्क पहले से कतारबद्ध माइक्रोटास्क के पीछे क्यों चलता है और कई टास्क स्रोतों की उपेक्षा करता है → एन्क्यू समय, श्रेणी और कतार की स्थिति को पंक्ति-दर-पंक्ति एनोटेट करें।
  • setTimeout(..., 0) को तात्कालिक कहना → एक समाप्त हो चुका टाइमर केवल भविष्य के टास्क को योग्य बनाता है और वर्तमान टास्क या चेकपॉइंट को प्रीमेम्प्ट नहीं कर सकता → सटीक समय का वादा किए बिना जल्द से जल्द पात्रता बताएं।
  • यह मान लेना कि प्रत्येक माइक्रोटास्क के बाद पेंटिंग होती है → एक चेकपॉइंट लगातार माइक्रोटास्क को खाली करता है, और बाद के रेंडरिंग अवसर को अभी भी छोड़ा जा सकता है → पूर्ण चेकपॉइंट के बाद पेंटिंग पर चर्चा करें।
  • await Promise.resolve() के साथ चंकिंग करना → निरंतरता अभी भी एक माइक्रोटास्क है और बाद के इनपुट या टाइमर टास्क को प्रवेश नहीं करने देती है → भविष्य के टास्क में निरंतरता को शेड्यूल करें।
  • "प्राथमिकता" के लिए अंतहीन queueMicrotask() कॉलों का उपयोग करना → वे अन्य टास्क और रेंडरिंग को बाधित करते हैं → माइक्रोटास्क को छोटे, सीमित कंसिस्टेंसी कार्य के लिए आरक्षित रखें।
  • भारी काम को requestAnimationFrame() में ले जाना → कॉलबैक अभी भी पेंट से पहले मेन थ्रेड पर चलता है, इसलिए भारी काम फ़्रेम में देरी करता है → rAF विज़ुअल कार्य को हल्का रखें और CPU कार्य को चंक करें या ऑफ़लोड करें।
  • बिना शर्त scheduler.yield() को कॉल करना → कुछ व्यापक रूप से उपयोग किए जाने वाले ब्राउज़र इसका समर्थन नहीं करते हैं → इसे फ़ीचर-डिटेक्ट करें और setTimeout() फ़ॉलबैक का परीक्षण करें।
  • केवल कुल अवधि को मापना → सामान्य थ्रूपुट अनुत्तरदायी इनपुट की लंबी अवधि को छिपा सकता है → इंटरैक्शन लेटेंसी, लंबे टास्क, फ़्रेम और कतार वृद्धि का भी निरीक्षण करें।
  • हर जगह एक निश्चित बैच आकार का उपयोग करना → प्रति-आइटम लागत, रिफ्रेश दर, और डिवाइस की गति भिन्न होती है → एक समय बजट के साथ शुरुआत करें और लक्षित वातावरण में इसे ट्यून करें।

अनुवर्ती प्रश्न और उत्तर

अनुवर्ती 1: पहले कौन सा चलता है, दो शून्य-विलंब टाइमर या एक क्लिक?

अकेले API नाम विभिन्न टास्क स्रोतों के बीच एक सार्वभौमिक क्रम स्थापित नहीं करते हैं। एक ब्राउज़र कई टास्क कतारें बनाए रख सकता है और एक ही टास्क स्रोत के भीतर क्रम को बनाए रखते हुए चलने योग्य कतारों में से चुन सकता है। स्पष्ट करें कि टाइमर कब योग्य हुए, क्लिक कब हुआ, और पेज पर और क्या चल रहा था, फिर लक्षित कार्यान्वयन का निरीक्षण करें। नियतात्मक इंटरव्यू स्निपेट आमतौर पर इन स्थितियों को नियंत्रित करते हैं।

अनुवर्ती 2: यदि scheduler.yield() एक Promise लौटाता है, तो यह एक वास्तविक यील्ड क्यों है?

महत्वपूर्ण गुण यह है कि API समाधान (resolution) को कैसे शेड्यूल करता है, न कि रिटर्न प्रकार। पहले से हल किए गए Promise के बाद की निरंतरता वर्तमान माइक्रोटास्क चेकपॉइंट में शामिल होती है। scheduler.yield() एक बाद के प्राथमिकता वाले टास्क को शेड्यूल करता है जो इसके Promise को हल करता है, जिससे निरंतरता से पहले इनपुट जैसे लंबित कार्यों को चलने की अनुमति मिलती है। इसे अभी भी फ़ीचर डिटेक्शन की आवश्यकता है, और छोटे-छोटे ऑपरेशनों में से प्रत्येक को एक अलग टास्क नहीं बनना चाहिए।

अनुवर्ती 3: क्या वर्कर में 100,000 रिकॉर्ड प्रोसेस करने से भी पेज फ़्रीज़ हो सकता है?

हाँ। अत्यधिक संदेशों, बड़े डेटा कॉपी, बार-बार DOM परिवर्तनों, या महंगे परिणाम मर्जिंग से मेन थ्रेड अभी भी ओवरलोड हो सकता है। प्रगति संदेशों को बैच करें, उनकी आवृत्ति सीमित करें, ट्रांसफ़रेबल ऑब्जेक्ट्स या एक उचित शेयर्ड-मेमोरी प्रोटोकॉल का मूल्यांकन करें, और मेन थ्रेड पर केवल आवश्यक विज़ुअल परिवर्तनों को कमिट करें।

अनुवर्ती 4: queueMicrotask() का उपयोग कब किया जाना चाहिए?

इसका उपयोग छोटे, सीमित काम के लिए करें जो वर्तमान सिंक्रोनस लॉजिक के बाद लेकिन अन्य घटनाओं से पहले चलना चाहिए, जैसे कि सिंक्रोनस कैश हिट और Promise-आधारित मिस को समान कॉलबैक ऑर्डरिंग देना, या एक आंतरिक लाइब्रेरी नोटिफ़िकेशन को बैच करना। यह लंबे काम का शेड्यूलर नहीं है। समाप्ति नियम और कार्य सीमा दोनों बताएं।

अनुवर्ती 5: आप चंक किए गए ऑपरेशन को कैसे रद्द करेंगे?

प्रत्येक बैच की शुरुआत में और प्रत्येक यील्ड के बाद एक AbortSignal की जांच करें, रिकॉर्ड लेना बंद करें, और किसी पुराने ऑपरेशन को नए अनुरोध की प्रगति या परिणाम को अधिलेखित करने से रोकें। एक वर्कर डिज़ाइन को रद्दीकरण संदेश और देर से आने वाले परिणामों को त्यागने के नियम की आवश्यकता होती है। जो जांचें बहुत विरल (sparse) होती हैं वे धीमी प्रतिक्रिया देती हैं; प्रत्येक छोटे ऑपरेशन के आसपास की जांचें ओवरहेड जोड़ती हैं, इसलिए बैच सीमाओं पर मापें।

अनुवर्ती 6: क्या requestAnimationFrame() तत्काल पेंट की गारंटी देता है?

नहीं। यह भविष्य के रीपेंट से पहले एक कॉलबैक का अनुरोध करता है, जबकि ब्राउज़र दृश्यता और रेंडरिंग नीति के आधार पर रेंडरिंग को शेड्यूल या छोड़ सकता है। अधिकांश ब्राउज़र बैकग्राउंड टैब में इन कॉलबैक को रोक देते हैं। कॉलबैक चलने पर भी, इसके अंदर का लंबा काम वास्तविक पेंट में देरी करना जारी रखता है।

सार्वजनिक स्रोत

संबंधित प्रश्न