लंबे टास्क ऑप्टिमाइज़ करें

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

टास्क क्या होता है?

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

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

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

मुख्य थ्रेड क्या है?

मुख्य थ्रेड वह जगह है जहां ब्राउज़र में ज़्यादातर टास्क चलते हैं. यहां आपकी लिखी गई करीब-करीब सारी JavaScript लागू होती है.

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

Chrome के DevTools के परफ़ॉर्मेंस प्रोफ़ाइलर में एक लंबा टास्क. टास्क को ब्लॉक करने वाले हिस्से (50 मिलीसेकंड से ज़्यादा) पर, लाल रंग की डायगनल धारियां बनी होती हैं.
Chrome के परफ़ॉर्मेंस प्रोफ़ाइलर में दिखाया गया लंबा टास्क. लंबे टास्क, टास्क के कोने में लाल रंग के त्रिभुज के ज़रिए दिखाए जाते हैं. टास्क के ब्लॉक किए गए हिस्से में लाल रंग की तिरछी धारियों का पैटर्न दिखाया गया है.

इससे बचने के लिए, हर लंबे टास्क को छोटे-छोटे टास्क में बांटें, ताकि हर टास्क को पूरा होने में कम समय लगे. इसे लंबे टास्क ब्रेक अप करना कहा जाता है.

एक लंबे टास्क बनाम एक ही टास्क को छोटे-छोटे टास्क में बांट दिया जाता है. बड़ा टास्क
    एक बड़ा रेक्टैंगल है. छोटे टास्क में पांच छोटे बॉक्स हैं, जिनकी लंबाई के हिसाब से
    लंबे टास्क पूरे हो रहे हैं.
एक बड़े टास्क बनाम उसी टास्क को पांच छोटे टास्क में बांटा गया है.

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

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

टास्क को मैनेज करने से जुड़ी रणनीतियां

JavaScript हर फ़ंक्शन को एक टास्क मानता है, क्योंकि यह टास्क के काम करने के तरीके के run-to-complete मॉडल का इस्तेमाल करता है. इसका मतलब है कि नीचे दिए गए उदाहरण की तरह, कई दूसरे फ़ंक्शन को कॉल करने वाला फ़ंक्शन तब तक चलना चाहिए, जब तक सभी फ़ंक्शन पूरे नहीं हो जाते. इससे ब्राउज़र धीमा हो जाता है:

function saveSettings () { //This is a long task.
  validateForm();
  showSpinner();
  saveToDatabase();
  updateUI();
  sendAnalytics();
}
Chrome के परफ़ॉर्मेंस प्रोफ़ाइलर में दिखाया गयाSaveSettings फ़ंक्शन. टॉप-लेवल फ़ंक्शन पांच अन्य फ़ंक्शन को कॉल करता है, लेकिन सभी काम एक लंबे टास्क में होते हैं, जो मुख्य थ्रेड को ब्लॉक करता है.
एक फ़ंक्शन saveSettings(), जो पांच फ़ंक्शन को कॉल करता है. यह काम, एक लंबे मोनोलिथिक टास्क के हिस्से के तौर पर किया जाता है.

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

कोड को मैन्युअल तरीके से आगे बढ़ाएं

काम के फ़ंक्शन को setTimeout() को पास करके, कुछ टास्क को प्रोसेस होने से रोका जा सकता है. यह तब भी काम करता है, जब आप 0 का टाइम आउट तय करें.

function saveSettings () {
  // Do critical work that is user-visible:
  validateForm();
  showSpinner();
  updateUI();

  // Defer work that isn't user-visible to a separate task:
  setTimeout(() => {
    saveToDatabase();
    sendAnalytics();
  }, 0);
}

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

function processData () {
  for (const item of largeDataArray) {
    // Process the individual item here.
  }
}

अच्छी बात यह है कि कुछ ऐसे एपीआई हैं जिनकी मदद से, बाद में किए जाने वाले टास्क के लिए, कोड एक्ज़ीक्यूट किया जा सकता है. हमारा सुझाव है कि तेज़ी से टाइम आउट पाने के लिए, postMessage() का इस्तेमाल करें.

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

ईल्ड पॉइंट बनाने के लिए, async/await का इस्तेमाल करें

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

इसे करने का सबसे आसान तरीका है, Promise को कॉल करें, ताकि यह रिज़ॉल्व हो जाए: setTimeout():

function yieldToMain () {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

saveSettings() फ़ंक्शन में, हर चरण के बाद मुख्य थ्रेड में पहुंचा जा सकता है. ऐसा तब होता है, जब हर फ़ंक्शन कॉल के बाद yieldToMain() फ़ंक्शन को await किया जाता है. इससे, आपके लंबे टास्क को कई टास्क में बांट दिया जाता है:

async function saveSettings () {
  // Create an array of functions to run:
  const tasks = [
    validateForm,
    showSpinner,
    saveToDatabase,
    updateUI,
    sendAnalytics
  ]

  // Loop over the tasks:
  while (tasks.length > 0) {
    // Shift the first task off the tasks array:
    const task = tasks.shift();

    // Run the task:
    task();

    // Yield to the main thread:
    await yieldToMain();
  }
}

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

Chrome के परफ़ॉर्मेंस प्रोफ़ाइलर में भी
    वहीसेव सेटिंग फ़ंक्शन मौजूद है, जो अब उपलब्ध है.
    इस टास्क को अब पांच अलग-अलग टास्क में बांट दिया गया है. हर फ़ंक्शन के लिए एक टास्क होता है.
saveSettings() फ़ंक्शन अब अपने चाइल्ड फ़ंक्शन को अलग-अलग टास्क के तौर पर चलाता है.

एक खास शेड्यूलर एपीआई

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

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

ब्राउज़र सहायता

  • 94
  • 94
  • x

सोर्स

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

postTask() एपीआई की तीन प्राथमिकताएं उपलब्ध हैं:

  • सबसे कम प्राथमिकता वाले टास्क के लिए, 'background'.
  • सामान्य प्राथमिकता वाले टास्क के लिए, 'user-visible'. अगर कोई priority सेट नहीं है, तो यह डिफ़ॉल्ट सेटिंग लागू होती है.
  • जिन ज़रूरी टास्क को ज़्यादा प्राथमिकता पर चलाना है उनके लिए 'user-blocking'.

इस उदाहरण में दिए गए कोड में तीन टास्क को सबसे ज़्यादा प्राथमिकता पर और बाकी दो टास्क को सबसे कम प्राथमिकता पर चलाने के लिए, postTask() API का इस्तेमाल किया गया है:

function saveSettings () {
  // Validate the form at high priority
  scheduler.postTask(validateForm, {priority: 'user-blocking'});

  // Show the spinner at high priority:
  scheduler.postTask(showSpinner, {priority: 'user-blocking'});

  // Update the database in the background:
  scheduler.postTask(saveToDatabase, {priority: 'background'});

  // Update the user interface at high priority:
  scheduler.postTask(updateUI, {priority: 'user-blocking'});

  // Send analytics data in the background:
  scheduler.postTask(sendAnalytics, {priority: 'background'});
};

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

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

आपके पास ऐसे अलग-अलग TaskController ऑब्जेक्ट को इंस्टैंशिएट करने का विकल्प भी होता है जो टास्क के बीच प्राथमिकता शेयर करते हैं. इसमें, अलग-अलग TaskController इंस्टेंस के लिए, ज़रूरत के हिसाब से प्राथमिकताएं बदलने की सुविधा भी शामिल है.

आने वाले scheduler.yield() एपीआई का इस्तेमाल करके, पहले से मौजूद यील्ड का इस्तेमाल जारी रखने के लिए

अहम पॉइंट: scheduler.yield() के बारे में ज़्यादा जानकारी के लिए, इस सुविधा को मुफ़्त में आज़माने की अवधि खत्म होने के बाद और इसके बारे में जानकारी के बारे में पढ़ें.

शेड्यूलर एपीआई में scheduler.yield() जोड़ने का सुझाव दिया गया है. यह एपीआई खास तौर पर, ब्राउज़र के मुख्य थ्रेड में पहुंचने के लिए डिज़ाइन किया गया है. इसका इस्तेमाल, इस पेज पर पहले दिखाए गए yieldToMain() फ़ंक्शन से मिलता-जुलता है:

async function saveSettings () {
  // Create an array of functions to run:
  const tasks = [
    validateForm,
    showSpinner,
    saveToDatabase,
    updateUI,
    sendAnalytics
  ]

  // Loop over the tasks:
  while (tasks.length > 0) {
    // Shift the first task off the tasks array:
    const task = tasks.shift();

    // Run the task:
    task();

    // Yield to the main thread with the scheduler
    // API's own yielding mechanism:
    await scheduler.yield();
  }
}

यह कोड काफ़ी हद तक जाना-पहचाना है, लेकिन yieldToMain() के बजाय, यह await scheduler.yield() का इस्तेमाल करता है.

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

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

user-blocking प्राथमिकता की वजह से, priority: 'user-blocking' के साथ scheduler.postTask() का इस्तेमाल करते रहने की संभावना भी ज़्यादा होती है. इसलिए, scheduler.yield() के ज़्यादा से ज़्यादा उपलब्ध होने तक इसका इस्तेमाल विकल्प के तौर पर किया जा सकता है.

setTimeout() (या priority: 'user-visible' के साथ scheduler.postTask() या साफ़ तौर पर priority का इस्तेमाल न करने पर) का इस्तेमाल करने पर, सूची में सबसे पीछे वाला टास्क शेड्यूल हो जाता है. इससे, टास्क को इस तरह से जारी रखने से पहले पूरा नहीं किया जा सकता.

isInputPending() से इनपुट मिलने पर फ़ायदा पाएं

ब्राउज़र सहायता

  • 87
  • 87
  • x
  • x

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

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

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

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

नतीजा

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

  • उपयोगकर्ता को मिलने वाले अहम टास्क के लिए, मुख्य थ्रेड में आगे बढ़ा जा सकता है.
  • scheduler.yield() आज़माएं.
  • postTask() की मदद से टास्क को प्राथमिकता दें.
  • आखिर में, अपने फ़ंक्शन में जितना हो सके उतना कम काम करें.

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

इस दस्तावेज़ की तकनीकी जांच करने के लिए, फ़िलिप वॉल्टन को विशेष धन्यवाद.

थंबनेल की इमेज, Unsplash से ली गई है. यह इमेज अमिराली मिरासेमिअन के सौजन्य से मिली है.