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

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

ग़ैर-ज़रूरी काम को शेड्यूल करने के लिए 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 का इस्तेमाल करना

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

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

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

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

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

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

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

सबसे सही तरीका यह है कि requestAnimationFrame कॉलबैक में सिर्फ़ डीओएम बदलाव किए जाएं, क्योंकि इन्हें ब्राउज़र इस तरह के काम को ध्यान में रखकर शेड्यूल करता है. इसका मतलब है कि हमारे कोड को दस्तावेज़ के एक फ़्रैगमेंट का इस्तेमाल करना होगा. इसके बाद, इसे अगले requestAnimationFrame कॉलबैक में जोड़ा जा सकता है. अगर आपने वीडीओएम लाइब्रेरी का इस्तेमाल किया है, तो बदलाव करने के लिए 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;
}

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

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

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

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

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

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