आपको "मुख्य थ्रेड को ब्लॉक न करें" और "लंबे टास्क को छोटे-छोटे टास्क में बांटें" के बारे में बताया गया है. हालांकि, इन बातों का क्या मतलब है?
पब्लिश किया गया: 30 सितंबर, 2022, पिछली बार अपडेट किया गया: 19 दिसंबर, 2024
JavaScript ऐप्लिकेशन को तेज़ बनाए रखने के लिए, आम तौर पर ये सलाह दी जाती है:
- "मुख्य थ्रेड को ब्लॉक न करें."
- "बड़े टास्क को छोटे-छोटे टास्क में बांटें."
यह एक बेहतरीन सलाह है, लेकिन इसमें क्या काम करना होगा? कम JavaScript का इस्तेमाल करना अच्छा है, लेकिन क्या इससे यूज़र इंटरफ़ेस (यूआई) अपने-आप ज़्यादा रिस्पॉन्सिव हो जाते हैं? हो सकता है, नहीं भी हो सकता है.
JavaScript में टास्क को ऑप्टिमाइज़ करने का तरीका समझने के लिए, आपको सबसे पहले यह जानना होगा कि टास्क क्या होते हैं और ब्राउज़र उन्हें कैसे हैंडल करता है.
टास्क क्या होता है?
टास्क, ब्राउज़र के ज़रिए किया जाने वाला कोई भी काम होता है. इसमें रेंडरिंग, एचटीएमएल और सीएसएस पार्स करना, JavaScript चलाना, और अन्य तरह के काम शामिल हैं. इन पर आपका सीधा कंट्रोल नहीं होता. इन सभी में, आपकी लिखी गई JavaScript, टास्क का सबसे बड़ा सोर्स होती है.

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

मुख्य थ्रेड को ज़्यादा देर तक ब्लॉक होने से रोकने के लिए, लंबे टास्क को कई छोटे-छोटे टास्क में बांटा जा सकता है.

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

ऊपर दिए गए डायग्राम में, उपयोगकर्ता के इंटरैक्शन से क्यू किया गया इवेंट हैंडलर, शुरू होने से पहले एक लंबे टास्क के पूरा होने का इंतज़ार करता है. इससे इंटरैक्शन में देरी होती है. इस स्थिति में, उपयोगकर्ता को लैग की समस्या दिख सकती है. सबसे नीचे, इवेंट हैंडलर तुरंत शुरू हो सकता है. साथ ही, इंटरैक्शन तुरंत हो सकता है.
अब आपको पता चल गया है कि टास्क को छोटे-छोटे हिस्सों में बांटना क्यों ज़रूरी है. अब JavaScript में ऐसा करने का तरीका जानें.
टास्क मैनेज करने की रणनीतियां
सॉफ़्टवेयर आर्किटेक्चर में, काम को छोटे-छोटे फ़ंक्शन में बांटने की सलाह दी जाती है:
function saveSettings () {
validateForm();
showSpinner();
saveToDatabase();
updateUI();
sendAnalytics();
}
इस उदाहरण में, saveSettings()
नाम का एक फ़ंक्शन है. यह फ़ंक्शन, पांच फ़ंक्शन को कॉल करता है. ये फ़ंक्शन, फ़ॉर्म की पुष्टि करने, स्पिनर दिखाने, ऐप्लिकेशन के बैकएंड को डेटा भेजने, यूज़र इंटरफ़ेस को अपडेट करने, और आंकड़ों को भेजने के लिए कॉल किए जाते हैं.
आइडिया के तौर पर, saveSettings()
को अच्छी तरह से डिज़ाइन किया गया है. अगर आपको इनमें से किसी फ़ंक्शन को डीबग करना है, तो प्रोजेक्ट ट्री पर जाकर यह पता लगाया जा सकता है कि हर फ़ंक्शन क्या करता है. काम को इस तरह से बांटने पर, प्रोजेक्ट को नेविगेट करना और बनाए रखना आसान हो जाता है.
हालांकि, यहां एक समस्या यह है कि JavaScript इन सभी फ़ंक्शन को अलग-अलग टास्क के तौर पर नहीं चलाता है, क्योंकि ये saveSettings()
फ़ंक्शन के अंदर एक्ज़ीक्यूट होते हैं. इसका मतलब है कि ये पांचों फ़ंक्शन, एक टास्क के तौर पर काम करेंगे.

saveSettings()
, जो पांच फ़ंक्शन को कॉल करता है. इस काम को एक बड़े टास्क के तौर पर किया जाता है. इसलिए, जब तक पांचों फ़ंक्शन पूरे नहीं हो जाते, तब तक कोई विज़ुअल जवाब नहीं मिलता.
सबसे अच्छे मामले में, इनमें से सिर्फ़ एक फ़ंक्शन, टास्क की कुल अवधि में 50 मिलीसेकंड या उससे ज़्यादा का समय जोड़ सकता है. सबसे खराब स्थिति में, इनमें से ज़्यादातर टास्क बहुत ज़्यादा समय तक चल सकते हैं. ऐसा खास तौर पर उन डिवाइसों पर होता है जिनमें संसाधन सीमित होते हैं.
इस मामले में, saveSettings()
को उपयोगकर्ता के क्लिक से ट्रिगर किया जाता है. साथ ही, जब तक पूरा फ़ंक्शन नहीं चल जाता, तब तक ब्राउज़र कोई जवाब नहीं दिखा पाता. इसलिए, इस लंबे टास्क का नतीजा यह होता है कि यूज़र इंटरफ़ेस (यूआई) धीमा हो जाता है और रिस्पॉन्स नहीं देता. इसे खराब पेज के रिस्पॉन्स में लगने वाला समय (आईएनपी) के तौर पर मेज़र किया जाएगा.
कोड को मैन्युअल तरीके से चलाने में देरी करना
यह पक्का करने के लिए कि उपयोगकर्ता को दिखने वाले ज़रूरी टास्क और यूज़र इंटरफ़ेस (यूआई) के जवाब, कम प्राथमिकता वाले टास्क से पहले पूरे हो जाएं, मुख्य थ्रेड को कुछ समय के लिए रोकें. इससे ब्राउज़र को ज़्यादा ज़रूरी टास्क चलाने का मौका मिलेगा.
डेवलपर, टास्क को छोटे-छोटे हिस्सों में बांटने के लिए 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()
के बाद, ब्राउज़र हर अतिरिक्त setTimeout()
के लिए कम से कम पांच मिलीसेकंड की देरी करना शुरू कर देगा.
setTimeout
का एक और नुकसान यह है कि जब कोड को बाद के टास्क में चलाने के लिए, setTimeout
का इस्तेमाल करके मुख्य थ्रेड को यिल्ड किया जाता है, तो वह टास्क, क्यू के आखिर में जुड़ जाता है. अगर कोई अन्य टास्क पूरा होने के लिए इंतज़ार कर रहा है, तो वह टाले गए कोड से पहले चलेगा.
एक खास यिल्डिंग एपीआई: scheduler.yield()
scheduler.yield()
एक ऐसा एपीआई है जिसे खास तौर पर ब्राउज़र में मुख्य थ्रेड को मैनेज करने के लिए डिज़ाइन किया गया है.
यह भाषा के लेवल का सिंटैक्स या खास कंस्ट्रक्ट नहीं है. scheduler.yield()
सिर्फ़ एक ऐसा फ़ंक्शन है जो Promise
दिखाता है. इसे आने वाले समय में हल किया जाएगा. Promise
के हल हो जाने के बाद, उससे जुड़ा कोई भी कोड (चाहे वह साफ़ तौर पर .then()
चेन में हो या एसिंक फ़ंक्शन में await
करने के बाद) उस फ़्यूचर टास्क में चलेगा.
असल में: await scheduler.yield()
डालें. इससे फ़ंक्शन उस पॉइंट पर रुक जाएगा और मुख्य थ्रेड को चालू कर देगा. फ़ंक्शन के बाकी हिस्से को फ़ंक्शन का जारी रहना कहा जाता है. इसे नए इवेंट-लूप टास्क में चलाने के लिए शेड्यूल किया जाएगा. जब वह टास्क शुरू होगा, तब इंतज़ार किया जा रहा प्रॉमिस पूरा हो जाएगा. इसके बाद, फ़ंक्शन वहीं से काम करना शुरू कर देगा जहाँ उसे छोड़ा गया था.
async function saveSettings () {
// Do critical work that is user-visible:
validateForm();
showSpinner();
updateUI();
// Yield to the main thread:
await scheduler.yield()
// Work that isn't user-visible, continued in a separate task:
saveToDatabase();
sendAnalytics();
}

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

scheduler.yield()
का इस्तेमाल करने पर, यह सुविधा वहीं से शुरू होती है जहां आपने छोड़ा था. इसके बाद, यह अन्य टास्क पर जाती है.
अलग-अलग ब्राउज़र पर काम करने की सुविधा
scheduler.yield()
अभी सभी ब्राउज़र पर काम नहीं करता है. इसलिए, फ़ॉलबैक की ज़रूरत होती है.
एक तरीका यह है कि आप अपने बिल्ड में scheduler-polyfill
को शामिल करें. इसके बाद, scheduler.yield()
का सीधे तौर पर इस्तेमाल किया जा सकता है. पॉलीफ़िल, टास्क शेड्यूल करने वाले अन्य फ़ंक्शन पर वापस जाने की सुविधा को मैनेज करेगा, ताकि यह अलग-अलग ब्राउज़र पर एक जैसा काम करे.
इसके अलावा, कम जटिल वर्शन को कुछ लाइनों में लिखा जा सकता है. इसमें सिर्फ़ setTimeout
का इस्तेमाल किया जाता है. अगर scheduler.yield()
उपलब्ध नहीं है, तो फ़ॉलबैक के तौर पर Promise में रैप किया जाता है.
function yieldToMain () {
if (globalThis.scheduler?.yield) {
return scheduler.yield();
}
// Fall back to yielding with setTimeout.
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
scheduler.yield()
की सुविधा के बिना काम करने वाले ब्राउज़र में, प्राथमिकता के आधार पर जारी रखने की सुविधा काम नहीं करेगी. हालांकि, ब्राउज़र को रिस्पॉन्सिव बनाए रखने के लिए, यह सुविधा काम करती रहेगी.
आखिर में, ऐसे मामले भी हो सकते हैं जहां आपके कोड को मुख्य थ्रेड को नहीं रोकना चाहिए. ऐसा तब होता है, जब उसके काम को प्राथमिकता नहीं दी जाती. उदाहरण के लिए, कोई ऐसा पेज जिस पर पहले से ही काम चल रहा है. ऐसे में, मुख्य थ्रेड को रोकने से कुछ समय के लिए काम पूरा नहीं हो पाएगा. ऐसे में, scheduler.yield()
को एक तरह का प्रोग्रेसिव एन्हांसमेंट माना जा सकता है: scheduler.yield()
की सुविधा वाले ब्राउज़र में इसका इस्तेमाल किया जा सकता है. अगर यह सुविधा उपलब्ध नहीं है, तो इसे जारी रखा जा सकता है.
इसे सुविधा का पता लगाकर और एक लाइन में लिखे गए कोड में एक माइक्रोटास्क के पूरा होने का इंतज़ार करके किया जा सकता है:
// Yield to the main thread if scheduler.yield() is available.
await globalThis.scheduler?.yield?.();
scheduler.yield()
की मदद से, लंबे समय तक चलने वाले काम को छोटे-छोटे हिस्सों में बांटना
scheduler.yield()
का इस्तेमाल करने के इन तरीकों का फ़ायदा यह है कि इसे किसी भी async
फ़ंक्शन में इस्तेमाल किया जा सकता है.await
उदाहरण के लिए, अगर आपको कई ऐसे जॉब चलाने हैं जो अक्सर एक लंबे टास्क में बदल जाते हैं, तो टास्क को छोटे-छोटे हिस्सों में बांटने के लिए, यिल्ड का इस्तेमाल किया जा सकता है.
async function runJobs(jobQueue) {
for (const job of jobQueue) {
// Run the job:
job();
// Yield to the main thread:
await yieldToMain();
}
}
runJobs()
को जारी रखने को प्राथमिकता दी जाएगी. हालांकि, इससे ज़्यादा प्राथमिकता वाले काम, जैसे कि उपयोगकर्ता के इनपुट का जवाब विज़ुअल तरीके से देना, पूरा किया जा सकेगा. इसके लिए, संभावित तौर पर लंबी चलने वाली नौकरियों के खत्म होने का इंतज़ार नहीं करना पड़ेगा.
हालांकि, इससे बेहतर तरीके से यिल्डिंग नहीं हो पाती. scheduler.yield()
तेज़ी से और असरदार तरीके से काम करता है. हालांकि, इसमें कुछ ओवरहेड होता है. अगर jobQueue
में कुछ काम बहुत कम समय के लिए हैं, तो ओवरहेड की वजह से, काम को पूरा करने में लगने वाला समय बढ़ सकता है. ऐसा इसलिए, क्योंकि काम को पूरा करने के बजाय, उसे शुरू करने और बंद करने में ज़्यादा समय लग सकता है.
एक तरीका यह है कि सभी टास्क को एक बैच में रखा जाए. इसके बाद, अगर पिछले टास्क को पूरा हुए काफ़ी समय हो गया है, तो ही अगले टास्क को पूरा किया जाए. टास्क को लंबे समय तक चलने वाले टास्क बनने से रोकने के लिए, आम तौर पर 50 मिलीसेकंड की समयसीमा तय की जाती है. हालांकि, रिस्पॉन्सिवनेस और जॉब क्यू को पूरा करने में लगने वाले समय के बीच समझौता करके, इसे अडजस्ट किया जा सकता है.
async function runJobs(jobQueue, deadline=50) {
let lastYield = performance.now();
for (const job of jobQueue) {
// Run the job:
job();
// If it's been longer than the deadline, yield to the main thread:
if (performance.now() - lastYield > deadline) {
await yieldToMain();
lastYield = performance.now();
}
}
}
इस वजह से, जॉब को इस तरह से बांटा जाता है कि उन्हें पूरा होने में ज़्यादा समय न लगे. हालांकि, रनर सिर्फ़ हर 50 मिलीसेकंड में मुख्य थ्रेड को प्रोसेस करता है.

isInputPending()
का इस्तेमाल न करें
isInputPending()
एपीआई की मदद से यह पता लगाया जा सकता है कि किसी उपयोगकर्ता ने पेज से इंटरैक्ट करने की कोशिश की है या नहीं. साथ ही, यह सिर्फ़ तब नतीजे दिखाता है, जब कोई इनपुट प्रोसेस होना बाकी हो.
इससे, अगर कोई इनपुट प्रोसेस नहीं किया जा रहा है, तो JavaScript को जारी रखने की अनुमति मिलती है. ऐसा इसलिए, ताकि वह टास्क कतार के आखिर में न पहुंच जाए. इससे परफ़ॉर्मेंस में काफ़ी सुधार हो सकता है. इसके बारे में Intent to Ship में बताया गया है. यह उन साइटों के लिए फ़ायदेमंद है जो मुख्य थ्रेड पर वापस नहीं आ पाती हैं.
हालांकि, उस एपीआई के लॉन्च होने के बाद से, हमें यिल्डिंग के बारे में ज़्यादा जानकारी मिली है. खास तौर पर, आईएनपी के लॉन्च होने के बाद. हम अब इस एपीआई का इस्तेमाल करने का सुझाव नहीं देते हैं. इसके बजाय, हम इनपुट के लंबित होने या न होने से कोई फ़र्क़ नहीं पड़ता. इसकी कई वजहें हैं:
- ऐसा हो सकता है कि
isInputPending()
, कुछ मामलों में उपयोगकर्ता के इंटरैक्ट करने के बावजूदfalse
को गलत तरीके से वापस कर दे. - सिर्फ़ इनपुट के मामले में ही टास्क को रोकना नहीं चाहिए. रिस्पॉन्सिव वेब पेज उपलब्ध कराने के लिए, ऐनिमेशन और अन्य सामान्य यूज़र इंटरफ़ेस अपडेट भी उतने ही ज़रूरी हो सकते हैं.
- इसके बाद, ज़्यादा बेहतर यिल्डिंग एपीआई लॉन्च किए गए. इनसे यिल्डिंग से जुड़ी समस्याओं को हल किया जा सकता है. जैसे,
scheduler.postTask()
औरscheduler.yield()
.
नतीजा
टास्क मैनेज करना मुश्किल है. हालांकि, ऐसा करने से यह पक्का किया जा सकता है कि आपका पेज, उपयोगकर्ता के इंटरैक्शन का जवाब ज़्यादा तेज़ी से दे. टास्क मैनेज करने और उन्हें प्राथमिकता देने के लिए, कोई एक सलाह नहीं दी जा सकती. इसके लिए, कई अलग-अलग तकनीकों का इस्तेमाल किया जाता है. हम आपको फिर से बता दें कि टास्क मैनेज करते समय, आपको इन मुख्य बातों का ध्यान रखना होगा:
- उपयोगकर्ता के लिए ज़रूरी टास्क के लिए, मुख्य थ्रेड को प्राथमिकता दें.
scheduler.yield()
का इस्तेमाल करें. इससे, क्रॉस-ब्राउज़र फ़ॉलबैक के साथ-साथ, एर्गोनॉमिक तरीके से उपज मिलती है और प्राथमिकता के आधार पर जारी रखने की सुविधा मिलती है- आखिर में, अपने फ़ंक्शन में कम से कम काम करें.
scheduler.yield()
, टास्क शेड्यूलिंग से जुड़े scheduler.postTask()
, और टास्क को प्राथमिकता देने के बारे में ज़्यादा जानने के लिए, Prioritized Task Scheduling API के दस्तावेज़ देखें.
इनमें से एक या उससे ज़्यादा टूल की मदद से, आपको अपने ऐप्लिकेशन में काम को इस तरह से व्यवस्थित करना चाहिए कि उपयोगकर्ता की ज़रूरतों को प्राथमिकता दी जा सके. साथ ही, यह पक्का किया जा सके कि कम ज़रूरी काम भी पूरा हो जाए. इससे उपयोगकर्ताओं को बेहतर अनुभव मिलेगा. साथ ही, उन्हें ज़्यादा तेज़ी से जवाब मिलेंगे और वे इसका ज़्यादा आनंद ले पाएंगे.
इस गाइड की तकनीकी जांच करने के लिए, फ़िलिप वॉल्टन को खास तौर पर धन्यवाद.
थंबनेल इमेज, Unsplash से ली गई है. इसका क्रेडिट Amirali Mirhashemian को जाता है.