आपको "मुख्य थ्रेड को ब्लॉक न करें" और "लंबे टास्क को बांटें" कहा गया है, लेकिन इनका क्या मतलब है?
JavaScript ऐप्लिकेशन को तेज़ रखने के लिए, आम तौर पर ये सुझाव दिए जाते हैं:
- "मुख्य थ्रेड को ब्लॉक न करें."
- "लंबे टास्क को छोटे-छोटे हिस्सों में बांटें."
यह एक अच्छी सलाह है, लेकिन इसमें क्या काम करना है? कम JavaScript शिप करना अच्छा है, लेकिन क्या इससे यूज़र इंटरफ़ेस अपने-आप ज़्यादा रिस्पॉन्सिव हो जाते हैं? हो सकता है, लेकिन ऐसा भी हो सकता है कि न हो.
JavaScript में टास्क को ऑप्टिमाइज़ करने का तरीका जानने के लिए, आपको सबसे पहले यह जानना होगा कि टास्क क्या होते हैं और ब्राउज़र उन्हें कैसे मैनेज करता है.
टास्क क्या होता है?
टास्क, ब्राउज़र का कोई भी अलग काम होता है. इसमें एचटीएमएल और सीएसएस को रेंडर करना, पार्स करना, JavaScript चलाना, और दूसरे तरह के ऐसे काम शामिल हैं जिन पर आपका सीधा कंट्रोल नहीं हो सकता. इन सभी में से, आपका लिखा गया JavaScript शायद टास्क का सबसे बड़ा सोर्स है.
JavaScript से जुड़े टास्क, परफ़ॉर्मेंस पर कई तरीकों से असर डालते हैं:
- जब कोई ब्राउज़र स्टार्टअप के दौरान कोई JavaScript फ़ाइल डाउनलोड करता है, तो वह उस JavaScript को पार्स और कंपाइल करने के लिए टास्क को सूची में जोड़ता है, ताकि उसे बाद में चलाया जा सके.
- पेज के खुले रहने के दौरान, JavaScript के काम करने पर टास्क सूची में जोड़े जाते हैं. जैसे, इवेंट हैंडलर के ज़रिए इंटरैक्शन बढ़ाना, JavaScript से चलने वाले ऐनिमेशन, और बैकग्राउंड गतिविधि, जैसे कि आंकड़ों का कलेक्शन.
वेब वर्कर्स और मिलते-जुलते एपीआई को छोड़कर, यह सारा काम मुख्य थ्रेड पर होता है.
मुख्य थ्रेड क्या है?
मुख्य थ्रेड में ब्राउज़र में ज़्यादातर टास्क चलते हैं. साथ ही, इसमें आपके लिखे गए ज़्यादातर JavaScript को लागू किया जाता है.
मुख्य थ्रेड एक बार में सिर्फ़ एक टास्क प्रोसेस कर सकता है. जिस टास्क को पूरा करने में 50 मिलीसेकंड से ज़्यादा समय लगता है उसे लॉन्ग टास्क कहा जाता है. 50 मिलीसेकंड से ज़्यादा समय वाले टास्क के लिए, टास्क के कुल समय में से 50 मिलीसेकंड घटाने पर, टास्क की ब्लॉकिंग अवधि मिलती है.
ब्राउज़र, किसी भी अवधि के टास्क के चलने के दौरान इंटरैक्शन को ब्लॉक कर देता है. हालांकि, जब तक टास्क बहुत ज़्यादा समय तक नहीं चलते, तब तक उपयोगकर्ता को इसकी जानकारी नहीं मिलती. जब कोई उपयोगकर्ता किसी पेज से इंटरैक्ट करने की कोशिश करता है, तो अगर उस पेज पर कई लंबे टास्क हैं, तो यूज़र इंटरफ़ेस काम नहीं करेगा. अगर मुख्य थ्रेड बहुत लंबे समय तक ब्लॉक रहता है, तो हो सकता है कि यूज़र इंटरफ़ेस काम न करे.
मुख्य थ्रेड को ज़्यादा देर तक ब्लॉक होने से बचाने के लिए, किसी लंबे टास्क को कई छोटे टास्क में बांटें.
यह ज़रूरी है, क्योंकि जब टास्क को अलग-अलग हिस्सों में बांटा जाता है, तो ब्राउज़र ज़्यादा प्राथमिकता वाले कामों को तुरंत पूरा कर सकता है. इनमें उपयोगकर्ता के इंटरैक्शन भी शामिल हैं. इसके बाद, बाकी बचे टास्क पूरे हो जाते हैं. इससे यह पक्का होता है कि आपने शुरुआत में जो काम लाइन में लगाया था वह पूरा हो गया है.
ऊपर दिए गए इलस्ट्रेशन में सबसे ऊपर, उपयोगकर्ता के इंटरैक्शन से लाइन में लगा हुआ इवेंट हैंडलर, एक लंबे टास्क के पूरा होने का इंतज़ार कर रहा है. इससे इंटरैक्शन में देरी होती है. इस स्थिति में, हो सकता है कि उपयोगकर्ता को ऐप्लिकेशन में रुकावट महसूस हुई हो. सबसे नीचे, इवेंट हैंडलर जल्दी से चलना शुरू कर सकता है. साथ ही, इंटरैक्शन तुरंत महसूस हो सकता है.
अब आपको पता है कि टास्क को अलग-अलग हिस्सों में बांटना क्यों ज़रूरी है. अब JavaScript में ऐसा करने का तरीका जानें.
टास्क मैनेज करने की रणनीतियां
सॉफ़्टवेयर आर्किटेक्चर में एक आम सलाह यह है कि अपने काम को छोटे फ़ंक्शन में बांटें:
function saveSettings () {
validateForm();
showSpinner();
saveToDatabase();
updateUI();
sendAnalytics();
}
इस उदाहरण में, saveSettings()
नाम का एक फ़ंक्शन है. यह फ़ंक्शन, फ़ॉर्म की पुष्टि करने, स्पिनर दिखाने, ऐप्लिकेशन के बैकएंड को डेटा भेजने, यूज़र इंटरफ़ेस को अपडेट करने, और आंकड़े भेजने के लिए पांच फ़ंक्शन को कॉल करता है.
saveSettings()
को कॉन्सेप्ट के हिसाब से सही तरीके से बनाया गया है. अगर आपको इनमें से किसी फ़ंक्शन को डीबग करना है, तो प्रोजेक्ट ट्री में जाकर यह पता लगाया जा सकता है कि हर फ़ंक्शन क्या करता है. इस तरह से काम को बांटने से, प्रोजेक्ट को नेविगेट और मैनेज करना आसान हो जाता है.
हालांकि, यहां एक संभावित समस्या यह है कि JavaScript इनमें से हर फ़ंक्शन को अलग-अलग टास्क के तौर पर नहीं चलाता, क्योंकि ये saveSettings()
फ़ंक्शन में लागू होते हैं. इसका मतलब है कि सभी पांच फ़ंक्शन एक टास्क के तौर पर चलेंगे.
सबसे अच्छे मामले में, इनमें से सिर्फ़ एक फ़ंक्शन भी टास्क की कुल अवधि में 50 मिलीसेकंड या उससे ज़्यादा का योगदान दे सकता है. सबसे खराब स्थिति में, ज़्यादा टास्क ज़्यादा समय तक चल सकते हैं. खास तौर पर, कम संसाधन वाले डिवाइसों पर.
कोड को मैन्युअल तरीके से बाद में चलाना
डेवलपर ने टास्क को छोटे-छोटे हिस्सों में बांटने के लिए, setTimeout()
का इस्तेमाल किया है. इस तकनीक की मदद से, फ़ंक्शन को 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);
}
इसे नतीजा देना कहा जाता है. यह उन फ़ंक्शन की सीरीज़ के लिए सबसे अच्छा काम करता है जिन्हें क्रम से चलाना ज़रूरी है.
हालांकि, ऐसा हो सकता है कि आपका कोड हमेशा इस तरह से व्यवस्थित न हो. उदाहरण के लिए, आपके पास बहुत ज़्यादा डेटा हो सकता है जिसे लूप में प्रोसेस करना ज़रूरी है. अगर कई बार दोहराव करना पड़ता है, तो उस टास्क को पूरा होने में काफ़ी समय लग सकता है.
function processData () {
for (const item of largeDataArray) {
// Process the individual item here.
}
}
डेवलपर के काम करने के तरीके की वजह से, यहां setTimeout()
का इस्तेमाल करना समस्या पैदा कर सकता है. साथ ही, डेटा के पूरे कलेक्शन को प्रोसेस होने में काफ़ी समय लग सकता है. भले ही, हर बार यह प्रोसेस तेज़ी से पूरी हो. इन सभी बातों को ध्यान में रखते हुए, यह कहा जा सकता है कि setTimeout()
इस काम के लिए सही टूल नहीं है. कम से कम, इस तरह इस्तेमाल करने पर ऐसा है.
Yiel points बनाने के लिए, async
/await
का इस्तेमाल करें
उपयोगकर्ता के लिए ज़रूरी टास्क, कम प्राथमिकता वाले टास्क से पहले पूरे हो सकें, इसके लिए टास्क की सूची में कुछ समय के लिए रुकावट डालकर, मुख्य थ्रेड को प्राथमिकता दी जा सकती है. इससे ब्राउज़र को ज़्यादा ज़रूरी टास्क चलाने का मौका मिलता है.
जैसा कि पहले बताया गया है, setTimeout
का इस्तेमाल मुख्य थ्रेड को यार्न देने के लिए किया जा सकता है. हालांकि, आसानी से पढ़ने और समझने के लिए, Promise
में setTimeout
को कॉल किया जा सकता है और उसके resolve
तरीके को कॉलबैक के तौर पर पास किया जा सकता है.
function yieldToMain () {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
yieldToMain()
फ़ंक्शन का फ़ायदा यह है कि इसे किसी भी async
फ़ंक्शन में 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();
}
}
इस वजह से, पहले जो एक ही टास्क था उसे अब अलग-अलग टास्क में बांटा गया है.
खास तौर पर शेड्यूलर के लिए बनाया गया एपीआई
setTimeout
, टास्क को अलग-अलग करने का एक असरदार तरीका है. हालांकि, इसका एक नुकसान भी हो सकता है: जब किसी कोड को बाद में चलाने के लिए, उसे मुख्य थ्रेड को सौंप दिया जाता है, तो वह टास्क सूची के आखिर में जुड़ जाता है.
अगर आपके पास अपने पेज के सभी कोड को कंट्रोल करने की सुविधा है, तो टास्क को प्राथमिकता देने की सुविधा के साथ अपना शेड्यूलर बनाया जा सकता है. हालांकि, तीसरे पक्ष की स्क्रिप्ट आपके शेड्यूलर का इस्तेमाल नहीं करेंगी. इसका मतलब है कि ऐसे माहौल में, काम को प्राथमिकता नहीं दी जा सकती. इसे सिर्फ़ छोटे-छोटे हिस्सों में बांटा जा सकता है या उपयोगकर्ता के इंटरैक्शन के हिसाब से इसे दिखाया जा सकता है.
शेड्यूलर एपीआई, postTask()
फ़ंक्शन की सुविधा देता है. इसकी मदद से, टास्क को बेहतर तरीके से शेड्यूल किया जा सकता है. यह ब्राउज़र को काम की प्राथमिकता तय करने में मदद करने का एक तरीका है, ताकि कम प्राथमिकता वाले टास्क मुख्य थ्रेड को मिल सकें. postTask()
, प्रॉमिस का इस्तेमाल करता है और priority
की तीन सेटिंग में से किसी एक को स्वीकार करता है:
'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'});
};
यहां टास्क की प्राथमिकता इस तरह से शेड्यूल की जाती है कि ब्राउज़र की प्राथमिकता वाले टास्क, जैसे कि उपयोगकर्ता इंटरैक्शन, ज़रूरत के हिसाब से बीच में काम कर सकें.
postTask()
का इस्तेमाल कैसे किया जा सकता है, इसका यह एक आसान उदाहरण है. अलग-अलग TaskController
ऑब्जेक्ट का इंस्टेंस बनाया जा सकता है, जो टास्क के बीच प्राथमिकताएं शेयर कर सकते हैं. साथ ही, ज़रूरत के हिसाब से अलग-अलग TaskController
इंस्टेंस की प्राथमिकताएं बदली जा सकती हैं.
scheduler.yield()
API का इस्तेमाल करके, पहले से मौजूद यील्ड के साथ जारी रखना
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()
का फ़ायदा यह है कि यह टास्क को जारी रखता है. इसका मतलब है कि अगर टास्क के बीच में ही 'बाहर निकलें' का विकल्प चुना जाता है, तो शेड्यूल किए गए अन्य टास्क, 'बाहर निकलें' के बाद उसी क्रम में जारी रहेंगे. इससे, तीसरे पक्ष की स्क्रिप्ट के कोड को आपके कोड के क्रम में रुकावट डालने से रोका जा सकता है.
isInputPending()
का इस्तेमाल न करें
isInputPending()
एपीआई से यह पता लगाया जा सकता है कि किसी उपयोगकर्ता ने किसी पेज से इंटरैक्ट करने की कोशिश की है या नहीं. यह एपीआई सिर्फ़ तब काम करता है, जब कोई इनपुट बाकी हो.
इससे, अगर कोई इनपुट बाकी नहीं है, तो JavaScript को जारी रखने में मदद मिलती है. ऐसा करने पर, JavaScript को टास्क की सूची में सबसे पीछे नहीं भेजा जाता. इससे परफ़ॉर्मेंस में काफ़ी सुधार हो सकता है. इस बारे में शिप करने का इंटेंट में बताया गया है. ऐसा उन साइटों के लिए किया जा सकता है जो मुख्य थ्रेड पर वापस नहीं आती हैं.
हालांकि, उस एपीआई के लॉन्च होने के बाद, हमें Yielding के बारे में ज़्यादा जानकारी मिली है. खास तौर पर, INP के लॉन्च होने के बाद. हमारा सुझाव है कि अब इस एपीआई का इस्तेमाल न करें. इसके बजाय, कई वजहों से इनपुट बाकी है या नहीं, इस बात से कोई फ़र्क़ नहीं पड़ता, इसलिए 'फ़ीड सबमिट करें' विकल्प का इस्तेमाल करें:
- कुछ मामलों में, उपयोगकर्ता के इंटरैक्ट करने के बावजूद,
isInputPending()
गलत तरीके सेfalse
दिखा सकता है. - इनपुट के अलावा, अन्य मामलों में भी टास्क जनरेट होने चाहिए. रिस्पॉन्सिव वेब पेज उपलब्ध कराने के लिए, ऐनिमेशन और यूज़र इंटरफ़ेस के अन्य नियमित अपडेट भी उतने ही ज़रूरी हो सकते हैं.
- इसके बाद, ज़्यादा बेहतर परफ़ॉर्म करने वाले एपीआई लॉन्च किए गए हैं. इनसे
scheduler.postTask()
औरscheduler.yield()
जैसी समस्याओं को हल करने में मदद मिलती है.
नतीजा
टास्क मैनेज करना मुश्किल है, लेकिन ऐसा करने से यह पक्का होता है कि आपका पेज, उपयोगकर्ता के इंटरैक्शन का तुरंत जवाब दे. टास्क मैनेज करने और उन्हें प्राथमिकता देने के लिए, कोई एक सलाह नहीं है. इसके लिए, कई अलग-अलग तरीके अपनाए जा सकते हैं. टास्क मैनेज करते समय, इन मुख्य बातों का ध्यान रखें:
- उपयोगकर्ता के लिए ज़रूरी टास्क के लिए, मुख्य थ्रेड को प्राथमिकता दें.
postTask()
का इस्तेमाल करके, टास्क को प्राथमिकता दें.scheduler.yield()
को आज़माएं.- आखिर में, अपने फ़ंक्शन में कम से कम काम करें.
इनमें से एक या उससे ज़्यादा टूल का इस्तेमाल करके, अपने ऐप्लिकेशन में काम को इस तरह से व्यवस्थित किया जा सकता है कि वह उपयोगकर्ता की ज़रूरतों को प्राथमिकता दे. साथ ही, यह भी पक्का किया जा सकता है कि कम ज़रूरी काम भी पूरे हो जाएं. इससे उपयोगकर्ताओं को बेहतर अनुभव मिलेगा. साथ ही, ऐप्लिकेशन को इस्तेमाल करना ज़्यादा आसान और मज़ेदार होगा.
इस गाइड की तकनीकी जांच करने के लिए, फ़िलिप वाल्टन का खास धन्यवाद.
थंबनेल इमेज, Unsplash से ली गई है. इमेज का क्रेडिट Amirali Mirhashemian को जाता है.