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

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

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

अच्छी बात यह है कि अब एक ऐसा एपीआई उपलब्ध है जिससे इन कामों में मदद मिल सकती है: requestIdleCallback. जिस तरह requestAnimationFrame का इस्तेमाल करके, हमने ऐनिमेशन को सही तरीके से शेड्यूल किया और 60fps तक पहुंचने की संभावना को बढ़ाया, उसी तरह 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 का इस्तेमाल करना

आइए, Analytics डेटा भेजने के लिए 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 मौजूद नहीं है, तो Analytics डेटा तुरंत भेजा जाना चाहिए. हालांकि, प्रोडक्शन ऐप्लिकेशन में, भेजने की प्रोसेस में देरी करना बेहतर होगा. ऐसा करने से, यह पक्का किया जा सकता है कि यह किसी भी इंटरैक्शन में रुकावट न डाले और ऐप्लिकेशन में रुकावट न आए.

DOM में बदलाव करने के लिए requestIdleCallback का इस्तेमाल करना

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

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

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

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

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

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

इस बात को ध्यान में रखते हुए, चलिए कोड देखते हैं:

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;
}

अगर सब कुछ ठीक रहा, तो अब हमें डीओएम में आइटम जोड़ते समय, बहुत कम रुकावट दिखेगी. शानदार!

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

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

Idle on!

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

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