isInput Pending() के साथ बेहतर JS शेड्यूलिंग

एक नया JavaScript API, जिससे लोडिंग की परफ़ॉर्मेंस और इनपुट रिस्पॉन्स के बीच के समझौते से बचा जा सकता है.

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

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

इस तरह के समझौते की ज़रूरत को खत्म करने के लिए, Facebook ने Chromium में isInputPending() एपीआई का सुझाव दिया और उसे लागू किया. इससे, बिना किसी समझौते के रिस्पॉन्सिवनेस को बेहतर बनाया जा सकता है. ऑरिजिन ट्रायल के सुझावों और राय के आधार पर, हमने एपीआई में कई अपडेट किए हैं. हमें यह बताते हुए खुशी हो रही है कि एपीआई अब Chromium 87 में डिफ़ॉल्ट रूप से शिप हो रहा है!

ब्राउज़र के साथ काम करना

ब्राउज़र के इस्तेमाल से जुड़ी सहायता

  • Chrome: 87.
  • Edge: 87.
  • Firefox: यह सुविधा काम नहीं करती.
  • Safari: यह सुविधा काम नहीं करती.

सोर्स

isInputPending() यह सुविधा, Chromium कोड वाले ब्राउज़र के वर्शन 87 से शुरू की गई है. किसी दूसरे ब्राउज़र ने एपीआई को शिप करने के इंटेंट का सिग्नल नहीं दिया है.

बैकग्राउंड

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

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

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

लंबे समय तक चलने वाले JS टास्क चलाने पर, ब्राउज़र के पास इवेंट डिस्पैच करने के लिए कम समय होता है.

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

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

एपीआई में लोगों की दिलचस्पी थी, इसलिए हमने Chromium में इस सुविधा को लागू करने और उसे उपलब्ध कराने के लिए, Chrome के अपने सहयोगियों के साथ साझेदारी की. Chrome के इंजीनियरों की मदद से, हमने ऑरिजिन ट्रायल के बाद पैच लागू किए. यह एक ऐसा तरीका है जिससे Chrome, एपीआई को पूरी तरह से रिलीज़ करने से पहले, बदलावों की जांच कर सकता है और डेवलपर से सुझाव/राय पा सकता है.

हमने अब ऑरिजिन ट्रायल और W3C वेब परफ़ॉर्मेंस वर्किंग ग्रुप के अन्य सदस्यों से सुझाव/राय ली है. साथ ही, एपीआई में बदलाव लागू कर दिए हैं.

उदाहरण: yieldier शेड्यूलर

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

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (performance.now() >= DEADLINE) {
    // Yield the event loop if we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

setTimeout() की मदद से, नए मैक्रोटास्क में बाद में processWorkQueue() को शुरू करके, हम ब्राउज़र को इनपुट के लिए कुछ हद तक रिस्पॉन्सिव बनाते हैं. इससे, काम फिर से शुरू होने से पहले, ब्राउज़र इवेंट हैंडलर चला सकता है. साथ ही, यह बिना किसी रुकावट के काम करता रहता है. हालांकि, इवेंट लूप को कंट्रोल करने वाले किसी दूसरे काम की वजह से, हो सकता है कि हम लंबे समय तक शेड्यूल न हों या इवेंट के इंतज़ार का समय QUANTUM मिलीसेकंड तक बढ़ जाए.

यह ठीक है, लेकिन क्या हम इसे और बेहतर बना सकते हैं? बिल्कुल!

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event, or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

navigator.scheduling.isInputPending() को कॉल करके, हम इनपुट का जवाब तेज़ी से दे पाते हैं. साथ ही, यह भी पक्का करते हैं कि डिसप्ले ब्लॉक करने की सुविधा बिना किसी रुकावट के काम करती रहे. अगर हमें काम पूरा होने तक, इनपुट (जैसे, पेंटिंग) के अलावा किसी और चीज़ को मैनेज करने में दिलचस्पी नहीं है, तो हम QUANTUM की लंबाई भी आसानी से बढ़ा सकते हैं.

डिफ़ॉल्ट रूप से, isInputPending() से "लगातार" इवेंट नहीं दिखाए जाते. इनमें mousemove, pointermove, और अन्य शामिल हैं. अगर आपको इनके लिए भी YIELD का इस्तेमाल करना है, तो कोई बात नहीं. isInputPending() के लिए कोई ऑब्जेक्ट उपलब्ध कराने पर, includeContinuous को true पर सेट करें. इसके बाद, हम आगे बढ़ सकते हैं:

const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event (any of them!), or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

हो गया! React जैसे फ़्रेमवर्क, मिलते-जुलते लॉजिक का इस्तेमाल करके, अपनी मुख्य शेड्यूलिंग लाइब्रेरी में isInputPending() के साथ काम करने की सुविधा जोड़ रहे हैं. उम्मीद है कि इससे इन फ़्रेमवर्क का इस्तेमाल करने वाले डेवलपर, isInputPending() का फ़ायदा पा सकेंगे. इसके लिए, उन्हें कोड में काफ़ी बदलाव करने की ज़रूरत नहीं पड़ेगी.

हार मानना हमेशा बुरा नहीं होता

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

ऐसे मामले भी हो सकते हैं जिनमें ब्राउज़र, बाकी बचे इनपुट इवेंट को सही तरीके से एट्रिब्यूट न कर पाए. खास तौर पर, क्रॉस-ऑरिजिन iframes के लिए जटिल क्लिप और मास्क सेट करने पर, गलत नेगेटिव रिपोर्ट मिल सकती है. इसका मतलब है कि इन फ़्रेम को टारगेट करते समय, isInputPending() अचानक गलत नतीजा दिखा सकता है. अगर आपकी साइट को स्टाइल वाले सबफ़्रेम के साथ इंटरैक्शन की ज़रूरत है, तो पक्का करें कि आपके पास अक्सर ये सबफ़्रेम उपलब्ध हों.

इवेंट लूप शेयर करने वाले अन्य पेजों का भी ध्यान रखें. Android के लिए Chrome जैसे प्लैटफ़ॉर्म पर, एक से ज़्यादा ऑरिजिन के लिए इवेंट लूप शेयर करना आम बात है. अगर इनपुट को किसी क्रॉस-ऑरिजिन फ़्रेम पर भेजा जाता है, तो isInputPending() कभी भी true नहीं दिखाएगा. इसलिए, बैकग्राउंड में चल रहे पेज, फ़ोरग्राउंड में चल रहे पेजों के रिस्पॉन्स में रुकावट डाल सकते हैं. Page Visibility API का इस्तेमाल करके, बैकग्राउंड में काम करते समय, आपको ज़्यादा बार डेटा भेजने, उसे रोकने या डेटा भेजने की प्रक्रिया को कम करने की ज़रूरत पड़ सकती है.

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

सुझाव/राय दें या शिकायत करें

  • is-input-pending रिपॉज़िटरी में, स्पेसिफ़िकेशन के बारे में सुझाव/राय दें या शिकायत करें.
  • स्पेसिफ़िकेशन के लेखकों में से एक @acomminos से Twitter पर संपर्क करें.

नतीजा

हमें यह बताते हुए खुशी हो रही है कि isInputPending() लॉन्च हो रहा है और डेवलपर इसका इस्तेमाल आज से कर सकते हैं. इस एपीआई के साथ, Facebook ने पहली बार नया वेब एपीआई बनाया है. साथ ही, इसे आइडिया से लेकर स्टैंडर्ड के प्रस्ताव तक ले गया है, ताकि इसे ब्राउज़र में उपलब्ध कराया जा सके. हम उन सभी लोगों का धन्यवाद करना चाहते हैं जिन्होंने हमें इस मुकाम तक पहुंचने में मदद की. साथ ही, Chrome की टीम के उन सभी लोगों को खास तौर पर धन्यवाद देना चाहते हैं जिन्होंने इस आइडिया को बेहतर बनाने और इसे लॉन्च करने में हमारी मदद की!

Unsplash पर Will H McMahan की हीरो फ़ोटो.