पेज के रिस्पॉन्स में लगने वाले समय (आईएनपी) को समझना

1. परिचय

पेज के रिस्पॉन्स में लगने वाले समय (आईएनपी) के बारे में जानने के लिए, एक इंटरैक्टिव डेमो और कोडलैब.

मुख्य थ्रेड पर इंटरैक्शन दिखाने वाला डायग्राम. जब ब्लॉक करने वाले टास्क चल रहे हों, तब उपयोगकर्ता कोई इनपुट देता है. इनपुट तब तक नहीं मिलता, जब तक ये टास्क पूरे नहीं हो जाते. इसके बाद, pointerup, mouseup, और click इवेंट लिसनर चलते हैं. इसके बाद, रेंडरिंग और पेंटिंग का काम तब तक शुरू होता है, जब तक अगला फ़्रेम नहीं दिखता

ज़रूरी शर्तें

आपको ये सब सीखने को मिलेगा

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

आपको इन चीज़ों की ज़रूरत पड़ेगी

  • एक ऐसा कंप्यूटर जिसमें GitHub से कोड को क्लोन करने और npm कमांड चलाने की सुविधा हो.
  • टेक्स्ट एडिटर.
  • इंटरैक्शन के सभी मेज़रमेंट काम करें, इसके लिए Chrome का नया वर्शन होना चाहिए.

2. सेट अप करें

कोड पाना और उसे चलाना

कोड, web-vitals-codelabs रिपॉज़िटरी में मौजूद है.

  1. अपने टर्मिनल में रेपो को क्लोन करें: git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
  2. क्लोन की गई डायरेक्ट्री पर जाएं: cd web-vitals-codelabs/understanding-inp
  3. डिपेंडेंसी इंस्टॉल करें: npm ci
  4. वेब सर्वर शुरू करें: npm run start
  5. अपने ब्राउज़र में http://localhost:5173/understanding-inp/ पर जाएं

ऐप्लिकेशन के बारे में खास जानकारी

पेज पर सबसे ऊपर, स्कोर काउंटर और बढ़ाएं बटन मौजूद है. यह रिऐक्टिविटी और रिस्पॉन्सिवनेस का एक क्लासिक डेमो है!

इस कोडलैब के लिए, डेमो ऐप्लिकेशन का स्क्रीनशॉट

बटन के नीचे चार मेज़रमेंट हैं:

  • आईएनपी: मौजूदा आईएनपी स्कोर, जो आम तौर पर सबसे खराब इंटरैक्शन होता है.
  • इंटरैक्शन: हाल ही के इंटरैक्शन का स्कोर.
  • एफ़पीएस: पेज के मुख्य थ्रेड के फ़्रेम प्रति सेकंड.
  • टाइमर: जैंक को विज़ुअलाइज़ करने में मदद करने के लिए, रनिंग टाइमर ऐनिमेशन.

इंटरैक्शन को मेज़र करने के लिए, एफ़पीएस और टाइमर एंट्री की बिलकुल भी ज़रूरत नहीं होती. इन्हें सिर्फ़ इसलिए जोड़ा गया है, ताकि रिस्पॉन्सिवनेस को विज़ुअलाइज़ करना थोड़ा आसान हो जाए.

इसे आज़माएं

Increment बटन से इंटरैक्ट करके देखें कि स्कोर बढ़ता है या नहीं. क्या हर बार बढ़ोतरी होने पर, आईएनपी और इंटरैक्शन की वैल्यू बदलती हैं?

आईएनपी से पता चलता है कि उपयोगकर्ता के इंटरैक्ट करने के बाद, पेज को रेंडर किए गए अपडेट को उपयोगकर्ता को दिखाने में कितना समय लगता है.

3. Chrome DevTools की मदद से इंटरैक्शन मेज़र करना

ज़्यादा टूल > डेवलपर टूल मेन्यू से DevTools खोलें. इसके लिए, पेज पर राइट क्लिक करें और जांच करें को चुनें या कीबोर्ड शॉर्टकट का इस्तेमाल करें.

परफ़ॉर्मेंस पैनल पर स्विच करें. इसका इस्तेमाल इंटरैक्शन को मेज़र करने के लिए किया जाएगा.

ऐप्लिकेशन के साथ DevTools के परफ़ॉर्मेंस पैनल का स्क्रीनशॉट

इसके बाद, परफ़ॉर्मेंस पैनल में इंटरैक्शन को कैप्चर करें.

  1. 'रिकॉर्ड करें' दबाएं.
  2. पेज के साथ इंटरैक्ट करें (बढ़ाएं बटन दबाएं).
  3. रिकॉर्डिंग बंद करें.

इसके बाद दिखने वाली टाइमलाइन में, आपको इंटरैक्शन ट्रैक दिखेगा. बाईं ओर मौजूद त्रिकोण पर क्लिक करके, इसे बड़ा करें.

DevTools के परफ़ॉर्मेंस पैनल का इस्तेमाल करके, इंटरैक्शन को रिकॉर्ड करने का ऐनिमेशन

दो इंटरैक्शन दिखते हैं. स्क्रोल करके या W बटन को दबाकर, दूसरे ऑब्जेक्ट पर ज़ूम इन करें.

DevTools के परफ़ॉर्मेंस पैनल का स्क्रीनशॉट. इसमें कर्सर को पैनल में मौजूद इंटरैक्शन पर कर्सर घुमाया जा रहा है. साथ ही, टूलटिप में इंटरैक्शन के कम समय की जानकारी दी गई है

इंटरैक्शन पर कर्सर घुमाने पर, आपको दिखेगा कि इंटरैक्शन तेज़ी से हुआ. इसमें प्रोसेसिंग में लगने वाले समय में कोई समय नहीं लगा. साथ ही, इनपुट में लगने वाले समय और प्रज़ेंटेशन में लगने वाले समय में कम से कम समय लगा. इनकी सटीक अवधि, आपकी मशीन की स्पीड पर निर्भर करेगी.

4. लंबे समय तक चलने वाले इवेंट लिसनर

index.js फ़ाइल खोलें और इवेंट लिसनर में मौजूद blockFor फ़ंक्शन को अनकमेंट करें.

पूरा कोड देखें: click_block.html

button.addEventListener('click', () => {
  blockFor(1000);
  score.incrementAndUpdateUI();
});

फ़ाइल सेव करें. सर्वर इस बदलाव को डिटेक्ट करेगा और आपके लिए पेज को रीफ़्रेश करेगा.

पेज पर दी गई जानकारी या विकल्पों के ज़रिए फिर से जवाब देने की कोशिश करें. अब इंटरैक्शन काफ़ी धीमे होंगे.

परफ़ॉर्मेंस ट्रेस

परफ़ॉर्मेंस पैनल में एक और रिकॉर्डिंग करें, ताकि यह देखा जा सके कि यह वहां कैसा दिखता है.

परफ़ॉर्मेंस पैनल में एक सेकंड का इंटरैक्शन

जिस इंटरैक्शन में पहले कम समय लगता था उसमें अब पूरा एक सेकंड लगता है.

इंटरैक्शन पर कर्सर घुमाने पर, आपको दिखेगा कि ज़्यादातर समय "प्रोसेसिंग में लगने वाला समय" में खर्च हुआ है. यह वह समय होता है जो इवेंट लिसनर कॉलबैक को लागू करने में लगता है. ब्लॉक करने वाला blockFor कॉल पूरी तरह से इवेंट लिसनर में होता है. इसलिए, इसमें समय लगता है.

5. एक्सपेरिमेंट: प्रोसेस होने की अवधि

इवेंट लिसनर के काम को फिर से व्यवस्थित करने के तरीके आज़माएं, ताकि आईएनपी पर पड़ने वाले असर को देखा जा सके.

सबसे पहले यूज़र इंटरफ़ेस (यूआई) अपडेट करना

अगर JavaScript कॉल के क्रम को बदल दिया जाए, तो क्या होगा? जैसे, पहले यूज़र इंटरफ़ेस (यूआई) को अपडेट करना और फिर ब्लॉक करना?

पूरा कोड देखें: ui_first.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  blockFor(1000);
});

क्या आपको यूज़र इंटरफ़ेस (यूआई) पहले दिखा? क्या इस क्रम से आईएनपी स्कोर पर असर पड़ता है?

ट्रेस करके इंटरैक्शन की जांच करें और देखें कि क्या कोई अंतर है.

अलग-अलग लिसनर

अगर इस काम को किसी अलग इवेंट लिसनर में ट्रांसफ़र कर दिया जाए, तो क्या होगा? एक इवेंट लिसनर में यूज़र इंटरफ़ेस (यूआई) को अपडेट करें और दूसरे लिसनर से पेज को ब्लॉक करें.

पूरा कोड देखें: two_click.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('click', () => {
  blockFor(1000);
});

अब यह परफ़ॉर्मेंस पैनल में कैसा दिखता है?

अलग-अलग तरह के इवेंट

ज़्यादातर इंटरैक्शन से कई तरह के इवेंट ट्रिगर होंगे. इनमें पॉइंटर या मुख्य इवेंट से लेकर, कर्सर घुमाने, फ़ोकस/धुंधला करने, और सिंथेटिक इवेंट शामिल हैं. जैसे, beforechange और beforeinput.

कई असली पेजों में, अलग-अलग इवेंट के लिए लिसनर होते हैं.

इवेंट लिसनर के लिए इवेंट टाइप बदलने पर क्या होता है? उदाहरण के लिए, क्या click इवेंट लिसनर में से किसी एक को pointerup या mouseup से बदला जा सकता है?

पूरा कोड देखें: diff_handlers.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('pointerup', () => {
  blockFor(1000);
});

यूज़र इंटरफ़ेस (यूआई) में कोई अपडेट नहीं किया गया है

अगर इवेंट लिसनर से यूज़र इंटरफ़ेस (यूआई) को अपडेट करने का कॉल हटा दिया जाए, तो क्या होगा?

पूरा कोड देखें: no_ui.html

button.addEventListener('click', () => {
  blockFor(1000);
  // score.incrementAndUpdateUI();
});

6. प्रोसेस होने की अवधि से जुड़े एक्सपेरिमेंट के नतीजे

परफ़ॉर्मेंस ट्रेस: पहले यूज़र इंटरफ़ेस (यूआई) को अपडेट करें

पूरा कोड देखें: ui_first.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  blockFor(1000);
});

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

परफ़ॉर्मेंस पैनल में एक सेकंड तक चलने वाला इंटरैक्शन

परफ़ॉर्मेंस ट्रेस: अलग-अलग लिसनर

पूरा कोड देखें: two_click.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('click', () => {
  blockFor(1000);
});

ध्यान रखें कि दोनों के काम करने के तरीके में कोई अंतर नहीं है. इंटरैक्शन में अब भी पूरा एक सेकंड लगता है.

अगर क्लिक इंटरैक्शन को ज़ूम इन किया जाए, तो आपको दिखेगा कि click इवेंट की वजह से, दो अलग-अलग फ़ंक्शन कॉल किए जा रहे हैं.

जैसा कि उम्मीद थी, पहला टास्क यानी यूज़र इंटरफ़ेस (यूआई) को अपडेट करना, बहुत तेज़ी से पूरा हो जाता है. वहीं, दूसरा टास्क पूरा होने में एक सेकंड लगता है. हालांकि, इन सभी वजहों से असली उपयोगकर्ता को इंटरैक्शन में देरी का सामना करना पड़ता है.

इस उदाहरण में, एक सेकंड के इंटरैक्शन को ज़ूम-इन करके दिखाया गया है. इसमें पहला फ़ंक्शन कॉल, एक मिलीसेकंड से भी कम समय में पूरा हो जाता है

परफ़ॉर्मेंस ट्रेस: अलग-अलग तरह के इवेंट

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('pointerup', () => {
  blockFor(1000);
});

ये नतीजे काफ़ी मिलते-जुलते हैं. इंटरैक्शन में अब भी पूरा एक सेकंड लगता है. फ़र्क़ सिर्फ़ इतना है कि यूज़र इंटरफ़ेस (यूआई) को अपडेट करने वाला छोटा click लिसनर, अब ब्लॉक करने वाले pointerup लिसनर के बाद चलता है.

इस उदाहरण में, एक सेकंड तक चलने वाले इंटरैक्शन को ज़ूम-इन करके दिखाया गया है. इसमें दिखाया गया है कि pointerup लिसनर के बाद, क्लिक इवेंट लिसनर को पूरा होने में एक मिलीसेकंड से भी कम समय लगता है

परफ़ॉर्मेंस ट्रेस: यूज़र इंटरफ़ेस (यूआई) अपडेट नहीं किया गया

पूरा कोड देखें: no_ui.html

button.addEventListener('click', () => {
  blockFor(1000);
  // score.incrementAndUpdateUI();
});
  • स्कोर अपडेट नहीं होता, लेकिन पेज अब भी अपडेट होता है!
  • ऐनिमेशन, सीएसएस इफ़ेक्ट, डिफ़ॉल्ट वेब कॉम्पोनेंट ऐक्शन (फ़ॉर्म इनपुट), टेक्स्ट एंट्री, और टेक्स्ट हाइलाइट करने की सुविधा अपडेट होती रहती है.

इस मामले में, क्लिक करने पर बटन चालू स्थिति में चला जाता है और फिर से वापस आ जाता है. इसके लिए, ब्राउज़र को पेंट करना पड़ता है. इसका मतलब है कि अब भी आईएनपी मौजूद है.

इवेंट लिसनर ने मुख्य थ्रेड को एक सेकंड के लिए ब्लॉक कर दिया था, ताकि पेज को रेंडर होने से रोका जा सके. इसलिए, इंटरैक्शन में अब भी पूरा एक सेकंड लगता है.

परफ़ॉर्मेंस पैनल की रिकॉर्डिंग लेने पर, इंटरैक्शन पहले की तरह ही दिखता है.

परफ़ॉर्मेंस पैनल में एक सेकंड तक चलने वाला इंटरैक्शन

सीखने लायक अहम बातें

किसी भी इवेंट लिसनर में चल रहा कोई भी कोड, इंटरैक्शन में देरी करेगा.

  • इसमें अलग-अलग स्क्रिप्ट से रजिस्टर किए गए लिसनर और लिसनर में चलने वाले फ़्रेमवर्क या लाइब्रेरी कोड शामिल हैं. जैसे, कोई स्टेट अपडेट जो कॉम्पोनेंट रेंडर को ट्रिगर करता है.
  • सिर्फ़ आपके कोड की नहीं, बल्कि तीसरे पक्ष की सभी स्क्रिप्ट की भी.

यह एक आम समस्या है!

आखिर में: अगर आपका कोड पेंट को ट्रिगर नहीं करता है, तो इसका मतलब यह नहीं है कि पेंट को पूरा होने के लिए, धीमे इवेंट लिसनर का इंतज़ार नहीं करना पड़ेगा.

7. एक्सपेरिमेंट: इनपुट में देरी

इवेंट लिसनर के बाहर लंबे समय तक चलने वाले कोड के बारे में क्या? उदाहरण के लिए:

  • अगर आपके पास देर से लोड होने वाला <script> है, जो लोड होने के दौरान पेज को अचानक ब्लॉक कर देता है.
  • क्या setInterval जैसा कोई एपीआई कॉल, पेज को समय-समय पर ब्लॉक करता है?

इवेंट लिसनर से blockFor को हटाएं और उसे setInterval() में जोड़ें:

पूरा कोड देखें: input_delay.html

setInterval(() => {
  blockFor(1000);
}, 3000);


button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

इस दिन क्या होगा?

8. इनपुट डिले से जुड़े एक्सपेरिमेंट के नतीजे

पूरा कोड देखें: input_delay.html

setInterval(() => {
  blockFor(1000);
}, 3000);


button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

setInterval को ब्लॉक करने वाले टास्क के चलने के दौरान, किसी बटन पर क्लिक को रिकॉर्ड करने से, इंटरैक्शन लंबे समय तक चलता है. भले ही, इंटरैक्शन में कोई ब्लॉक करने वाला काम न किया जा रहा हो!

ज़्यादा समय लेने वाले इन टास्क को अक्सर लॉन्ग टास्क कहा जाता है.

DevTools में इंटरैक्शन पर कर्सर घुमाने पर, आपको दिखेगा कि इंटरैक्शन का समय अब मुख्य रूप से इनपुट में लगने वाले समय की वजह से है, न कि प्रोसेसिंग में लगने वाले समय की वजह से.

DevTools के परफ़ॉर्मेंस पैनल में, एक सेकंड का ब्लॉकिंग टास्क, उस टास्क के दौरान होने वाला इंटरैक्शन, और 642 मिलीसेकंड का इंटरैक्शन दिखाया गया है. यह इंटरैक्शन, ज़्यादातर इनपुट में होने वाली देरी की वजह से होता है

ध्यान दें, इससे इंटरैक्शन पर हमेशा असर नहीं पड़ता! अगर टास्क के दौरान क्लिक नहीं किया जाता है, तो हो सकता है कि आपको इनाम मिल जाए. इस तरह की "रैंडम" छींकों को डीबग करना मुश्किल हो सकता है, क्योंकि इनसे कभी-कभी ही समस्याएं होती हैं.

इनका पता लगाने का एक तरीका है, ज़्यादा समय लेने वाले टास्क (या लंबे ऐनिमेशन फ़्रेम) और पेज रिस्पॉन्स बंद रहने का कुल समय को मेज़र करना.

9. प्रज़ेंटेशन धीरे-धीरे लोड हो रहा है

अब तक, हमने इनपुट में देरी या इवेंट लिसनर की मदद से, JavaScript की परफ़ॉर्मेंस को देखा है. हालांकि, रेंडरिंग नेक्स्ट पेंट पर और किन चीज़ों का असर पड़ता है?

पेज को महंगे इफ़ेक्ट के साथ अपडेट करना!

भले ही पेज अपडेट जल्दी हो जाए, लेकिन ब्राउज़र को उन्हें रेंडर करने के लिए अब भी ज़्यादा मेहनत करनी पड़ सकती है!

मुख्य थ्रेड पर:

  • ऐसे यूज़र इंटरफ़ेस (यूआई) फ़्रेमवर्क जिन्हें स्टेटस में बदलाव होने के बाद, अपडेट रेंडर करने की ज़रूरत होती है
  • डीओएम में बदलाव करने या कई महंगे सीएसएस क्वेरी सिलेक्टर को टॉगल करने से, स्टाइल, लेआउट, और पेंट से जुड़ी कई समस्याएं ट्रिगर हो सकती हैं.

मुख्य थ्रेड से बाहर:

  • जीपीयू इफ़ेक्ट को बेहतर बनाने के लिए सीएसएस का इस्तेमाल करना
  • बहुत बड़ी और हाई रिज़ॉल्यूशन वाली इमेज जोड़ना
  • जटिल सीन ड्रॉ करने के लिए, SVG/कैनवस का इस्तेमाल करना

वेब पर रेंडरिंग के अलग-अलग एलिमेंट का स्केच

RenderingNG

वेब पर आम तौर पर मिलने वाले कुछ उदाहरण:

  • कोई एसपीए साइट, जो किसी लिंक पर क्लिक करने के बाद पूरे डीओएम को फिर से बनाती है. हालांकि, वह शुरुआती विज़ुअल फ़ीडबैक देने के लिए नहीं रुकती.
  • खोज वाला ऐसा पेज जिसमें डाइनैमिक यूज़र इंटरफ़ेस के साथ, खोज के लिए जटिल फ़िल्टर उपलब्ध हों. हालांकि, ऐसा करने के लिए महंगे लिसनर का इस्तेमाल किया जाता हो.
  • गहरे रंग वाले मोड का टॉगल, जो पूरे पेज के लिए स्टाइल/लेआउट को ट्रिगर करता है

10. एक्सपेरिमेंट: प्रज़ेंटेशन में देरी

requestAnimationFrame ठीक से काम नहीं कर रहा है

आइए, requestAnimationFrame() एपीआई का इस्तेमाल करके, प्रज़ेंटेशन में होने वाली लंबी देरी को सिम्युलेट करें.

blockFor कॉल को requestAnimationFrame कॉलबैक में ले जाएं, ताकि यह इवेंट लिसनर के वापस आने के बाद चले:

पूरा कोड देखें: presentation_delay.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

इस दिन क्या होगा?

11. प्रज़ेंटेशन में देरी से जुड़े एक्सपेरिमेंट के नतीजे

पूरा कोड देखें: presentation_delay.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

इंटरैक्शन अब भी एक सेकंड का है, तो क्या हुआ?

requestAnimationFrame, अगले पेंट से पहले कॉलबैक का अनुरोध करता है. आईएनपी, इंटरैक्शन से लेकर अगले पेंट तक के समय को मेज़र करता है. इसलिए, requestAnimationFrame में मौजूद blockFor(1000), अगले पेंट को पूरे एक सेकंड तक ब्लॉक करता रहता है.

परफ़ॉर्मेंस पैनल में एक सेकंड तक चलने वाला इंटरैक्शन

हालांकि, इन दो बातों पर ध्यान दें:

  • होवर करने पर, आपको दिखेगा कि इंटरैक्शन का समय अब "प्रज़ेंटेशन में लगने वाले समय" में खर्च हो रहा है. ऐसा इसलिए होता है, क्योंकि इवेंट लिसनर के वापस आने के बाद, मुख्य थ्रेड को ब्लॉक किया जाता है.
  • मुख्य थ्रेड की गतिविधि का रूट, अब क्लिक इवेंट नहीं है, बल्कि "ऐनिमेशन फ़्रेम ट्रिगर हुआ" है.

12. इंटरैक्शन का विश्लेषण करना

इस टेस्ट पेज पर, रिस्पॉन्सिवनेस को विज़ुअल के तौर पर देखा जा सकता है. इसमें स्कोर, टाइमर, और काउंटर यूज़र इंटरफ़ेस (यूआई) शामिल है...हालांकि, सामान्य पेज की जांच करते समय यह ज़्यादा सटीक होता है.

जब इंटरैक्शन में ज़्यादा समय लगता है, तो हमेशा यह साफ़ तौर पर पता नहीं चलता कि इसकी वजह क्या है. क्या इसकी वजह यह है:

  • इनपुट में देरी?
  • इवेंट को प्रोसेस होने में कितना समय लगता है?
  • प्रज़ेंटेशन में देरी?

किसी भी पेज पर, DevTools का इस्तेमाल करके रिस्पॉन्सिवनेस को मेज़र किया जा सकता है. इसकी आदत डालने के लिए, यह तरीका आज़माएं:

  1. वेब पर उसी तरह नेविगेट करें जैसे आम तौर पर किया जाता है.
  2. DevTools के परफ़ॉर्मेंस पैनल के लाइव मेट्रिक व्यू में, इंटरैक्शन लॉग पर नज़र रखें.
  3. अगर आपको लगता है कि किसी इंटरैक्शन की परफ़ॉर्मेंस खराब है, तो उसे फिर से आज़माएं:
  • अगर आपको समस्या को दोहराने में परेशानी हो रही है, तो इंटरैक्शन लॉग का इस्तेमाल करके अहम जानकारी पाएं.
  • अगर आपको इस समस्या को दोहराना है, तो परफ़ॉर्मेंस पैनल में ट्रेस रिकॉर्ड करें.

देरी की सभी वजहें

पेज पर इन सभी समस्याओं को थोड़ा-थोड़ा करके जोड़ने की कोशिश करें:

पूरा कोड देखें: all_the_things.html

setInterval(() => {
  blockFor(1000);
}, 3000);

button.addEventListener('click', () => {
  blockFor(1000);
  score.incrementAndUpdateUI();

  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

इसके बाद, समस्याओं का पता लगाने के लिए कंसोल और परफ़ॉर्मेंस पैनल का इस्तेमाल करें!

13. एक्सपेरिमेंट: एसिंक्रोनस तरीके से काम करना

इंटरैक्शन के दौरान, बिना विज़ुअल वाले इफ़ेक्ट शुरू किए जा सकते हैं. जैसे, नेटवर्क के अनुरोध करना, टाइमर शुरू करना या ग्लोबल स्टेट को अपडेट करना. ऐसे में, जब ये इफ़ेक्ट आखिर में पेज को अपडेट करते हैं, तब क्या होता है?

जब तक इंटरैक्शन के बाद अगले पेंट को रेंडर करने की अनुमति है, तब तक इंटरैक्शन का मेज़रमेंट बंद रहता है. भले ही, ब्राउज़र यह तय करे कि उसे रेंडरिंग के नए अपडेट की ज़रूरत नहीं है.

इसे आज़माने के लिए, क्लिक लिसनर से यूज़र इंटरफ़ेस (यूआई) को अपडेट करना जारी रखें. हालांकि, ब्लॉक करने वाले काम को टाइम आउट से चलाएं.

पूरा कोड देखें: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

अब क्या होगा?

14. एसिंक्रोनस तरीके से काम करने के प्रयोग के नतीजे

पूरा कोड देखें: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

एक सेकंड के लंबे टास्क के साथ 27 मिलीसेकंड का इंटरैक्शन, जो अब ट्रेस में बाद में हो रहा है

यूज़र इंटरफ़ेस (यूआई) अपडेट होने के तुरंत बाद मुख्य थ्रेड उपलब्ध हो जाता है. इसलिए, अब इंटरैक्शन में कम समय लगता है. लंबे समय तक चलने वाला टास्क अब भी चलता है. हालांकि, यह पेंट के कुछ समय बाद चलता है. इससे उपयोगकर्ता को तुरंत यूज़र इंटरफ़ेस (यूआई) फ़ीडबैक मिलेगा.

सीख: अगर आप इसे हटा नहीं सकते, तो कम से कम इसे दूसरी जगह ले जाएं!

तरीके

क्या हम 100 मिलीसेकंड setTimeout से बेहतर कर सकते हैं? हम अब भी चाहते हैं कि कोड जल्द से जल्द रन हो. अगर ऐसा नहीं होता, तो हमें उसे हटा देना चाहिए!

लक्ष्य:

  • इंटरैक्शन incrementAndUpdateUI() चलेगा.
  • blockFor() को जल्द से जल्द चलाया जाएगा. हालांकि, यह अगले पेंट को ब्लॉक नहीं करेगा.
  • इससे, "मैजिक टाइम आउट" के बिना, अनुमानित व्यवहार मिलता है.

ऐसा करने के कुछ तरीके यहां दिए गए हैं:

  • setTimeout(0)
  • Promise.then()
  • requestAnimationFrame
  • requestIdleCallback
  • scheduler.postTask()

"requestPostAnimationFrame"

requestAnimationFrame अपने-आप काम करता है. यह अगले पेंट से पहले चलने की कोशिश करेगा और आम तौर पर, इंटरैक्शन को धीमा कर देगा. हालांकि, requestAnimationFrame + setTimeout, requestPostAnimationFrame के लिए एक आसान पॉलीफ़िल बनाता है. यह अगले पेंट के बाद कॉलबैक को चलाता है.

पूरा कोड देखें: raf+task.html

function afterNextPaint(callback) {
  requestAnimationFrame(() => {
    setTimeout(callback, 0);
  });
}

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  afterNextPaint(() => {
    blockFor(1000);
  });
});

एर्गोनॉमिक्स के लिए, इसे प्रॉमिस में भी रैप किया जा सकता है:

पूरा कोड देखें: raf+task2.html

async function nextPaint() {
  return new Promise(resolve => afterNextPaint(resolve));
}

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();

  await nextPaint();
  blockFor(1000);
});

15. एक से ज़्यादा इंटरैक्शन (और रेज क्लिक)

ज़्यादा समय लेने वाले टास्क को इधर-उधर करने से मदद मिल सकती है. हालांकि, ये टास्क अब भी पेज को ब्लॉक करते हैं. इससे आने वाले समय में होने वाले इंटरैक्शन के साथ-साथ, पेज के कई अन्य ऐनिमेशन और अपडेट पर भी असर पड़ता है.

पेज के एसिंक ब्लॉकिंग वर्क वर्शन को फिर से आज़माएं. इसके अलावा, अगर आपने पिछले चरण में काम को बाद में करने के लिए अपना वैरिएशन बनाया है, तो उसे आज़माएं:

पूरा कोड देखें: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

अगर तेज़ी से कई बार क्लिक किया जाए, तो क्या होगा?

परफ़ॉर्मेंस ट्रेस

हर क्लिक के लिए, एक सेकंड का टास्क सूची में जोड़ दिया जाता है. इससे यह पक्का होता है कि मुख्य थ्रेड को कुछ समय के लिए ब्लॉक किया गया है.

मुख्य थ्रेड में कई सेकंड लंबे टास्क मौजूद हैं. इनकी वजह से, इंटरैक्शन में 800 मिलीसेकंड तक की देरी हो रही है

जब ज़्यादा समय लेने वाले ये काम, नए क्लिक के साथ ओवरलैप होते हैं, तो इंटरैक्शन धीमे हो जाते हैं. भले ही, इवेंट लिसनर तुरंत नतीजे दिखाता हो. हमने इनपुट में देरी वाले पिछले एक्सपेरिमेंट जैसी ही स्थिति बनाई है. हालांकि, इस बार इनपुट में देरी setInterval की वजह से नहीं, बल्कि पहले के इवेंट लिसनर से ट्रिगर हुए काम की वजह से हो रही है.

रणनीतियां

हम चाहते हैं कि ज़्यादा समय लेने वाले काम पूरी तरह से हटा दिए जाएं!

  • बेवजह इस्तेमाल किए गए कोड को पूरी तरह से हटा दें. खास तौर पर, स्क्रिप्ट को.
  • ज़्यादा समय लेने वाले टास्क को चलाने से बचने के लिए, कोड को ऑप्टिमाइज़ करें.
  • नए इंटरैक्शन आने पर, पुराने काम को रद्द करें.

16. पहली रणनीति: डिबाउंस

एक क्लासिक रणनीति. जब इंटरैक्शन तेज़ी से होते हैं और प्रोसेसिंग या नेटवर्क पर असर पड़ता है, तो जान-बूझकर काम शुरू करने में देरी करें, ताकि आप उसे रद्द करके फिर से शुरू कर सकें. यह पैटर्न, ऑटोकंप्लीट फ़ील्ड जैसे यूज़र इंटरफ़ेस के लिए मददगार होता है.

  • ज़्यादा समय लेने वाले काम को शुरू करने में देरी करने के लिए, setTimeout का इस्तेमाल करें. इसके लिए, टाइमर का इस्तेमाल करें. जैसे, 500 से 1,000 मिलीसेकंड.
  • ऐसा करते समय, टाइमर आईडी सेव करें.
  • अगर कोई नया इंटरैक्शन आता है, तो clearTimeout का इस्तेमाल करके पिछले टाइमर को रद्द करें.

पूरा कोड देखें: debounce.html

let timer;
button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  if (timer) {
    clearTimeout(timer);
  }
  timer = setTimeout(() => {
    blockFor(1000);
  }, 1000);
});

परफ़ॉर्मेंस ट्रेस

कई इंटरैक्शन, लेकिन उन सभी के नतीजे के तौर पर सिर्फ़ एक ज़्यादा समय लेने वाला काम

कई बार क्लिक करने के बावजूद, सिर्फ़ एक blockFor टास्क चलता है. यह टास्क तब तक इंतज़ार करता है, जब तक कि एक सेकंड के लिए कोई क्लिक न किया गया हो. ऐसे इंटरैक्शन जो एक साथ कई बार होते हैं, उनके लिए डिफ़ॉल्ट रूप से इस रणनीति का इस्तेमाल करना सबसे सही होता है. जैसे, टेक्स्ट इनपुट में टाइप करना या ऐसे आइटम टारगेट जिन पर एक साथ कई बार क्लिक होने की उम्मीद हो.

17. दूसरी रणनीति: लंबे समय तक चलने वाले काम में रुकावट डालना

हालांकि, ऐसा हो सकता है कि डीबाउंस पीरियड खत्म होने के तुरंत बाद कोई क्लिक हो जाए. ऐसे में, वह क्लिक ज़्यादा समय लेने वाले काम के बीच में होगा और इनपुट में देरी की वजह से इंटरैक्शन बहुत धीमा हो जाएगा.

अगर हमारे टास्क के बीच में कोई इंटरैक्शन आता है, तो हम अपने मौजूदा काम को रोककर नए इंटरैक्शन को तुरंत हैंडल करना चाहते हैं. हम ऐसा कैसे कर सकते हैं?

isInputPending जैसे कुछ एपीआई मौजूद हैं, लेकिन आम तौर पर, ज़्यादा समय लेने वाले कामों को छोटे-छोटे हिस्सों में बांटना बेहतर होता है.

बहुत सारे setTimeout

पहली कोशिश: कुछ आसान करें.

पूरा कोड देखें: small_tasks.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  requestAnimationFrame(() => {
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
  });
});

यह हर टास्क को अलग-अलग शेड्यूल करने की अनुमति देता है. इससे इनपुट को ज़्यादा प्राथमिकता दी जा सकती है!

कई इंटरैक्शन, लेकिन शेड्यूल किए गए सभी कामों को कई छोटे-छोटे टास्क में बांट दिया गया है

हम पांच क्लिक के लिए, पांच सेकंड के काम पर वापस आ गए हैं. हालांकि, हर क्लिक के लिए एक सेकंड के टास्क को 100 मिलीसेकंड के 10 टास्क में बांट दिया गया है. इस वजह से, उन टास्क के साथ ओवरलैप होने वाले कई इंटरैक्शन के बावजूद, किसी भी इंटरैक्शन में इनपुट में 100 मिलीसेकंड से ज़्यादा की देरी नहीं होती! ब्राउज़र, setTimeout के काम के मुकाबले, आने वाले इवेंट लिसनर को प्राथमिकता देता है. साथ ही, इंटरैक्शन रिस्पॉन्सिव बने रहते हैं.

यह रणनीति खास तौर पर तब काम आती है, जब अलग-अलग एंट्री पॉइंट शेड्यूल किए जाते हैं. जैसे, अगर आपके पास कई ऐसी सुविधाएं हैं जिन्हें ऐप्लिकेशन लोड होने के समय कॉल करना ज़रूरी है. स्क्रिप्ट को लोड करने और स्क्रिप्ट के आकलन के समय सब कुछ चलाने से, डिफ़ॉल्ट रूप से सब कुछ एक बड़े और ज़्यादा समय लेने वाले टास्क में चल सकता है.

हालांकि, यह रणनीति आपस में जुड़े कोड को अलग करने के लिए उतनी कारगर नहीं है. जैसे, शेयर की गई स्थिति का इस्तेमाल करने वाला for लूप.

अब yield() के साथ

हालांकि, हम आधुनिक async और await का इस्तेमाल करके, किसी भी JavaScript फ़ंक्शन में "यील्ड पॉइंट" आसानी से जोड़ सकते हैं.

उदाहरण के लिए:

पूरा कोड देखें: yieldy.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldy(ms) {
  const ms_per_part = 10;
  const parts = ms / ms_per_part;
  for (let i = 0; i < parts; i++) {
    await schedulerDotYield();

    blockFor(ms_per_part);
  }
}

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();
  await blockInPiecesYieldy(1000);
});

पहले की तरह, काम का एक हिस्सा पूरा होने के बाद मुख्य थ्रेड को वापस लाया जाता है. साथ ही, ब्राउज़र किसी भी इंटरैक्शन का जवाब दे पाता है. हालांकि, अब अलग-अलग setTimeout के बजाय सिर्फ़ एक await schedulerDotYield() की ज़रूरत होती है. इससे, for लूप के बीच में भी इसका इस्तेमाल करना आसान हो जाता है.

अब AbortContoller() के साथ

इससे काम तो बन गया, लेकिन हर इंटरैक्शन से ज़्यादा काम शेड्यूल हो जाता है. भले ही, नए इंटरैक्शन आए हों और उन्होंने उस काम को बदल दिया हो जो किया जाना चाहिए.

डीबाउंसिंग रणनीति की मदद से, हमने हर नए इंटरैक्शन के साथ पिछले टाइम आउट को रद्द कर दिया. क्या हम यहां भी ऐसा कुछ कर सकते हैं? ऐसा करने का एक तरीका है, AbortController() का इस्तेमाल करना:

पूरा कोड देखें: aborty.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldyAborty(ms, signal) {
  const parts = ms / 10;
  for (let i = 0; i < parts; i++) {
    // If AbortController has been asked to stop, abandon the current loop.
    if (signal.aborted) return;

    await schedulerDotYield();

    blockFor(10);
  }
}

let abortController = new AbortController();

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();

  abortController.abort();
  abortController = new AbortController();

  await blockInPiecesYieldyAborty(1000, abortController.signal);
});

क्लिक होने पर, यह blockInPiecesYieldyAborty for लूप को शुरू करता है. यह लूप, ज़रूरी काम करता है और समय-समय पर मुख्य थ्रेड को खाली करता है, ताकि ब्राउज़र नए इंटरैक्शन के लिए रिस्पॉन्सिव बना रहे.

जब दूसरा क्लिक आता है, तो पहले लूप को AbortController के साथ रद्द के तौर पर फ़्लैग किया जाता है और एक नया blockInPiecesYieldyAborty लूप शुरू होता है. अगली बार जब पहले लूप को फिर से चलाने के लिए शेड्यूल किया जाता है, तो उसे पता चलता है कि signal.aborted अब true है और वह आगे काम किए बिना तुरंत वापस आ जाता है.

मुख्य थ्रेड का काम अब कई छोटे-छोटे हिस्सों में बंटा है. इंटरैक्शन कम समय के लिए होते हैं और काम सिर्फ़ तब तक चलता है, जब तक ज़रूरी हो

18. नतीजा

ज़्यादा समय लेने वाले सभी कामों को छोटे-छोटे कामों में बांटने से, साइट नए इंटरैक्शन के लिए रिस्पॉन्सिव बन पाती है. इससे आपको शुरुआती फ़ीडबैक तुरंत देने में मदद मिलती है. साथ ही, चल रहे काम को रोकने जैसे फ़ैसले लेने में भी मदद मिलती है. कभी-कभी इसका मतलब, एंट्री पॉइंट को अलग-अलग टास्क के तौर पर शेड्यूल करना होता है. कभी-कभी इसका मतलब है कि जहां ज़रूरी हो वहां "रास्ता दें" पॉइंट जोड़ना.

याद रखें

  • आईएनपी सभी इंटरैक्शन को मेज़र करता है.
  • हर इंटरैक्शन को इनपुट से लेकर नेक्स्ट पेंट तक मेज़र किया जाता है. इससे पता चलता है कि उपयोगकर्ता को रिस्पॉन्सिवनेस कैसी दिखती है.
  • इनपुट में देरी, इवेंट प्रोसेसिंग में लगने वाला समय, और प्रज़ेंटेशन में देरी, इन सभी से इंटरैक्शन रिस्पॉन्सिवनेस पर असर पड़ता है.
  • DevTools की मदद से, आईएनपी और इंटरैक्शन ब्रेकडाउन को आसानी से मेज़र किया जा सकता है!

रणनीतियां

  • अपने पेजों पर, ज़्यादा समय लेने वाले कोड (ज़्यादा समय लेने वाले टास्क) का इस्तेमाल न करें.
  • अगले पेंट तक, इवेंट लिसनर से ग़ैर-ज़रूरी कोड हटा दें.
  • पक्का करें कि रेंडरिंग अपडेट, ब्राउज़र के लिए असरदार हो.

ज़्यादा जानें