requestIdleCallback का इस्तेमाल करना

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

ग़ैर-ज़रूरी काम शेड्यूल करने के लिए, requestIdleCallback का इस्तेमाल करना.

अच्छी बात यह है कि अब एक ऐसा एपीआई मौजूद है जिससे इस काम में मदद मिल सकती है: requestIdleCallback. जिस तरह requestAnimationFrame का इस्तेमाल करने से हमें ऐनिमेशन को सही तरीके से शेड्यूल करने और 60 FPS (फ़्रेम प्रति सेकंड) तक पहुंचने की सुविधा मिलती है, ठीक उसी तरह requestIdleCallback फ़्रेम के आखिर में खाली समय होने पर या उपयोगकर्ता कोई गतिविधि न करने पर, काम को शेड्यूल करेगा. इसका मतलब है कि लोगों को परेशान किए बिना अपना काम किया जा सकता है. यह Chrome 47 के रूप में उपलब्ध है, इसलिए आप इसे आज ही Chrome कैनरी का उपयोग करके आगे बढ़ा सकते हैं! यह एक्सपेरिमेंट के तौर पर उपलब्ध सुविधा है और इसकी खास जानकारी में अब भी बदलाव हुआ है. इसलिए, आने वाले समय में चीज़ें बदल सकती हैं.

मुझे requestIdleCallback का इस्तेमाल क्यों करना चाहिए?

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

चलिए, थोड़ा विस्तार से जानते हैं और देखते हैं कि हम इसका इस्तेमाल कैसे कर सकते हैं.

requestIdleCallback की जांच की जा रही है

यह requestIdleCallback का शुरुआती समय है. इसलिए, इसका इस्तेमाल करने से पहले आपको यह देखना चाहिए कि यह इस्तेमाल के लिए उपलब्ध है या नहीं:

if ('requestIdleCallback' in window) {
    // Use requestIdleCallback to schedule work.
} else {
    // Do what you’d do today.
}

इसके काम करने के तरीके की नकल भी की जा सकती है. इसके लिए, setTimeout पर वापस जाना ज़रूरी है:

window.requestIdleCallback =
    window.requestIdleCallback ||
    function (cb) {
    var start = Date.now();
    return setTimeout(function () {
        cb({
        didTimeout: false,
        timeRemaining: function () {
            return Math.max(0, 50 - (Date.now() - start));
        }
        });
    }, 1);
    }

window.cancelIdleCallback =
    window.cancelIdleCallback ||
    function (id) {
    clearTimeout(id);
    }

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

हालांकि, अभी के लिए यह मान लीजिए कि यह मौजूद है.

requestIdleCallback का इस्तेमाल करना

requestIdleCallback को कॉल करना requestAnimationFrame से काफ़ी मिलता-जुलता है, क्योंकि यह कॉलबैक फ़ंक्शन को अपने पहले पैरामीटर के तौर पर लेता है:

requestIdleCallback(myNonEssentialWork);

myNonEssentialWork को कॉल करने पर, इसे deadline ऑब्जेक्ट दिया जाएगा. इसमें एक ऐसा फ़ंक्शन होगा जो यह बताता है कि आपके काम के लिए कितना समय बचा है:

function myNonEssentialWork (deadline) {
    while (deadline.timeRemaining() > 0)
    doWorkIfNeeded();
}

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

function myNonEssentialWork (deadline) {
    while (deadline.timeRemaining() > 0 && tasks.length > 0)
    doWorkIfNeeded();

    if (tasks.length > 0)
    requestIdleCallback(myNonEssentialWork);
}

आपके फ़ंक्शन की गारंटी देना को कॉल किया जाता है

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

// Wait at most two seconds before processing events.
requestIdleCallback(processPendingAnalyticsEvents, { timeout: 2000 });

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

  • timeRemaining() शून्य दिखाएगा.
  • deadline ऑब्जेक्ट की didTimeout प्रॉपर्टी सही होगी.

अगर आपको लगता है कि didTimeout सही है, तो आप शायद बस काम चलाना चाहें और उसके साथ काम करना चाहें:

function myNonEssentialWork (deadline) {

    // Use any remaining time, or, if timed out, just run through the tasks.
    while ((deadline.timeRemaining() > 0 || deadline.didTimeout) &&
            tasks.length > 0)
    doWorkIfNeeded();

    if (tasks.length > 0)
    requestIdleCallback(myNonEssentialWork);
}

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

आंकड़े भेजने के लिए, requestIdleCallback का इस्तेमाल करना

चलिए, आंकड़ों का डेटा भेजने के लिए requestIdleCallback का इस्तेमाल करते हैं. इस मामले में, शायद हम नेविगेशन मेन्यू पर टैप करने जैसे किसी इवेंट को ट्रैक करना चाहें. हालांकि, आम तौर पर ये स्क्रीन पर ऐनिमेट होते हैं, इसलिए हम नहीं चाहेंगे कि यह इवेंट Google Analytics को तुरंत भेजा जाए. हम इवेंट भेजने और उनका अनुरोध करने के लिए एक कलेक्शन बनाएंगे. इसमें उन्हें आने वाले समय में भेजे जाने वाले इवेंट के लिए अनुरोध किया जाएगा:

var eventsToSend = [];

function onNavOpenClick () {

    // Animate the menu.
    menu.classList.add('open');

    // Store the event for later.
    eventsToSend.push(
    {
        category: 'button',
        action: 'click',
        label: 'nav',
        value: 'open'
    });

    schedulePendingEvents();
}

अब हमें किसी भी लंबित इवेंट को प्रोसेस करने के लिए requestIdleCallback का इस्तेमाल करना होगा:

function schedulePendingEvents() {

    // Only schedule the rIC if one has not already been set.
    if (isRequestIdleCallbackScheduled)
    return;

    isRequestIdleCallbackScheduled = true;

    if ('requestIdleCallback' in window) {
    // Wait at most two seconds before processing events.
    requestIdleCallback(processPendingAnalyticsEvents, { timeout: 2000 });
    } else {
    processPendingAnalyticsEvents();
    }
}

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

आखिर में, हमें वह फ़ंक्शन लिखना होगा जिसे requestIdleCallback एक्ज़ीक्यूट करेगा.

function processPendingAnalyticsEvents (deadline) {

    // Reset the boolean so future rICs can be set.
    isRequestIdleCallbackScheduled = false;

    // If there is no deadline, just run as long as necessary.
    // This will be the case if requestIdleCallback doesn’t exist.
    if (typeof deadline === 'undefined')
    deadline = { timeRemaining: function () { return Number.MAX_VALUE } };

    // Go for as long as there is time remaining and work to do.
    while (deadline.timeRemaining() > 0 && eventsToSend.length > 0) {
    var evt = eventsToSend.pop();

    ga('send', 'event',
        evt.category,
        evt.action,
        evt.label,
        evt.value);
    }

    // Check if there are more events still to send.
    if (eventsToSend.length > 0)
    schedulePendingEvents();
}

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

डीओएम में बदलाव करने के लिए, requestIdleCallback का इस्तेमाल करना

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

एक सामान्य फ़्रेम.

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

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

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

सबसे अच्छा तरीका यह है कि सिर्फ़ requestAnimationFrame कॉलबैक में डीओएम बदलाव करें, क्योंकि इसे ब्राउज़र से उसी तरह के काम को ध्यान में रखकर शेड्यूल किया जाता है. इसका मतलब है कि हमारे कोड को दस्तावेज़ के फ़्रैगमेंट का इस्तेमाल करना होगा. इसके बाद, इसे अगले requestAnimationFrame कॉलबैक में जोड़ा जा सकता है. अगर आपने VDOM लाइब्रेरी का इस्तेमाल किया है, तो बदलाव करने के लिए requestIdleCallback का इस्तेमाल किया जाएगा. हालांकि, आपको अगले requestAnimationFrame कॉलबैक में, डीओएम पैच को लागू करना होगा, न कि इस्तेमाल में न होने वाले कॉलबैक में.

इसे ध्यान में रखते हुए, आइए कोड पर एक नज़र डालें:

function processPendingElements (deadline) {

    // If there is no deadline, just run as long as necessary.
    if (typeof deadline === 'undefined')
    deadline = { timeRemaining: function () { return Number.MAX_VALUE } };

    if (!documentFragment)
    documentFragment = document.createDocumentFragment();

    // Go for as long as there is time remaining and work to do.
    while (deadline.timeRemaining() > 0 && elementsToAdd.length > 0) {

    // Create the element.
    var elToAdd = elementsToAdd.pop();
    var el = document.createElement(elToAdd.tag);
    el.textContent = elToAdd.content;

    // Add it to the fragment.
    documentFragment.appendChild(el);

    // Don't append to the document immediately, wait for the next
    // requestAnimationFrame callback.
    scheduleVisualUpdateIfNeeded();
    }

    // Check if there are more events still to send.
    if (elementsToAdd.length > 0)
    scheduleElementCreation();
}

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

function scheduleVisualUpdateIfNeeded() {

    if (isVisualUpdateScheduled)
    return;

    isVisualUpdateScheduled = true;

    requestAnimationFrame(appendDocumentFragment);
}

function appendDocumentFragment() {
    // Append the fragment and reset.
    document.body.appendChild(documentFragment);
    documentFragment = null;
}

हम ठीक से जानते हैं कि DOM में आइटम जोड़ते समय अब हमें बहुत कम जैंक दिखेंगे. बहुत बढ़िया!

अक्सर पूछे जाने वाले सवाल

  • क्या पॉलीफ़िल है? अफ़सोस की बात नहीं है, लेकिन अगर आप setTimeout पर पारदर्शी रीडायरेक्ट करना चाहते हैं, तो कुछ झिझक भी है. इस एपीआई के मौजूद होने की वजह यह है कि यह वेब प्लैटफ़ॉर्म के बीच असल गैप को कम करता है. गतिविधि की कमी का पता लगाना कठिन है, लेकिन फ़्रेम के आखिर में खाली समय का पता लगाने के लिए, कोई JavaScript API मौजूद नहीं है. इसलिए, आपको अनुमान लगाना ही होगा. setTimeout, setInterval या setImmediate जैसे एपीआई का इस्तेमाल, काम को शेड्यूल करने के लिए किया जा सकता है. हालांकि, इन्हें इस्तेमाल करने वालों को requestIdleCallback की तरह इंटरैक्शन से बचाने के लिए, समय नहीं दिया जाता.
  • अगर मैं समयसीमा पार कर दूं, तो क्या होगा? अगर timeRemaining() शून्य दिखाता है, लेकिन आपने उसे ज़्यादा समय तक चलाने का विकल्प चुना है, तो ऐसा किया जा सकता है. इस बात का कोई डर नहीं है कि ब्राउज़र आपका काम रोक देगा. हालांकि, ब्राउज़र आपको अपने उपयोगकर्ताओं के लिए बेहतर अनुभव देने के लिए समयसीमा देता है. अगर कोई ठोस वजह न हो, तो आपको हमेशा समयसीमा का पालन करना चाहिए.
  • क्या कोई ऐसा ज़्यादा से ज़्यादा मान है जो timeRemaining() दिखाएगा? हां, फ़िलहाल यह 50 मि॰से॰ है. किसी रिस्पॉन्सिव ऐप्लिकेशन के रखरखाव की कोशिश करते समय, उपयोगकर्ता के इंटरैक्शन के सभी जवाब 100 मि॰से॰ से कम में रखे जाने चाहिए. अगर उपयोगकर्ता 50 मि॰से॰ विंडो से इंटरैक्ट करता है, तो ज़्यादातर मामलों में, इस्तेमाल न किए जा रहे कॉलबैक को पूरा होने की अनुमति मिलनी चाहिए. साथ ही, ब्राउज़र को उपयोगकर्ता के इंटरैक्शन का जवाब देना चाहिए. अगर ब्राउज़र को लगता है कि उन्हें चलाने के लिए ज़रूरत के मुताबिक समय है, तो आपको कई ऐसे कॉलबैक मिल सकते हैं जो कुछ समय से इस्तेमाल में नहीं हैं.
  • क्या ऐसा कोई काम है जिसे मुझे requestIdleCallback में नहीं करना चाहिए? आम तौर पर, आपको जो काम करना होता है वह छोटे-छोटे हिस्सों (माइक्रोटास्क) में होना चाहिए, जिनमें ऐसी विशेषताएं शामिल हों जिनके बारे में अनुमान लगाया जा सकता हो. उदाहरण के लिए, खास तौर पर डीओएम बदलने पर इसे लागू करने का अनुमान लगाना मुश्किल होगा, क्योंकि इससे स्टाइल कैलकुलेशन, लेआउट, पेंटिंग, और कंपोज़िटिंग ट्रिगर हो जाएंगी. इसलिए, आपको ऊपर बताए गए तरीके से, requestAnimationFrame कॉलबैक में सिर्फ़ DOM बदलाव करने चाहिए. एक और चीज़ जिसे ध्यान में रखना ज़रूरी है, वह है प्रॉमिस को पूरा करना (या अस्वीकार करना). ऐसा इसलिए है, क्योंकि इस्तेमाल में न होने वाले कॉलबैक के खत्म होने के तुरंत बाद कॉलबैक एक्ज़ीक्यूट हो जाते हैं, भले ही सेशन में अब ज़्यादा समय न बचा हो.
  • क्या मुझे हमेशा फ़्रेम के आखिर में requestIdleCallback मिलेगा? नहीं, हमेशा नहीं. फ़्रेम के आखिर में या उपयोगकर्ता के इनऐक्टिव रहने की अवधि में, ब्राउज़र खाली समय होने पर, कॉलबैक को शेड्यूल करेगा. आपको यह उम्मीद नहीं करनी चाहिए कि कॉलबैक को हर फ़्रेम के हिसाब से कॉल किया जाएगा. अगर आपको इसे किसी तय समयसीमा में चलाना है, तो आपको टाइम आउट का इस्तेमाल करना चाहिए.
  • क्या एक से ज़्यादा requestIdleCallback कॉलबैक किए जा सकते हैं? हां, एक से ज़्यादा requestAnimationFrame कॉलबैक किए जा सकते हैं. हालांकि, यह याद रखना ज़रूरी है कि अगर आपका पहला कॉलबैक, कॉलबैक के दौरान बचे हुए समय का इस्तेमाल करता है, तो दूसरे कॉलबैक के लिए ज़्यादा समय नहीं बचेगा. इसके बाद, अन्य कॉलबैक को ब्राउज़र को चलाने से पहले, तब तक इंतज़ार करना होगा, जब तक कि ब्राउज़र को कुछ समय के लिए इस्तेमाल न किया जा रहा हो. आपको जिस काम को पूरा करना है उसके आधार पर, यह बेहतर हो सकता है कि कुछ समय से इस्तेमाल में न होने पर एक कॉलबैक का इस्तेमाल किया जाए. इसके बाद, काम को बाकी काम में बांटा जा सकता है. इसके अलावा, टाइम आउट का इस्तेमाल करके यह पक्का किया जा सकता है कि कोई भी कॉलबैक, समय के लिए भूखा न हो.
  • अगर मैं किसी दूसरे कॉलबैक के अंदर, इस्तेमाल न किए जा रहे नए कॉलबैक को सेट करूं, तो क्या होगा? यह नया कॉलबैक जल्द से जल्द चलने के लिए शेड्यूल किया जाएगा. यह प्रोसेस, अगले फ़्रेम से शुरू होगी, न कि मौजूदा फ़्रेम से.

कुछ समय से इस्तेमाल में नहीं है!

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

Chrome कैनरी में इसे देखें, अपने प्रोजेक्ट को आज़माकर देखें, और हमें बताएं कि आपको यह काम कैसे करना है!