मॉड्यूल वर्कर से वेब पर थ्रेड की सुविधा

वेब वर्कर में JavaScript मॉड्यूल की मदद से, अब भारी कामों को बैकग्राउंड थ्रेड में ले जाना आसान हो गया है.

जेसन मिलर
जेसन मिलर

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

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

यहां वर्कर के इस्तेमाल का एक उदाहरण दिया गया है. इसमें वर्कर स्क्रिप्ट, मुख्य थ्रेड के मैसेज सुनती है और खुद के मैसेज वापस भेजकर जवाब देती है:

page.js:

const worker = new Worker('worker.js');
worker.addEventListener('message', e => {
  console.log(e.data);
});
worker.postMessage('hello');

worker.js:

addEventListener('message', e => {
  if (e.data === 'hello') {
    postMessage('world');
  }
});

Web Worker API, ज़्यादातर ब्राउज़र में दस साल से ज़्यादा समय से उपलब्ध है. इसका मतलब है कि कर्मचारियों को ब्राउज़र के लिए बेहतरीन सपोर्ट मिलता है और वे अच्छी तरह ऑप्टिमाइज़ किए जाते हैं. इसका मतलब यह भी है कि वे JavaScript मॉड्यूल के लंबे समय से पुराने वर्शन पर काम कर रहे हैं. वर्कर को डिज़ाइन करते समय कोई मॉड्यूल सिस्टम नहीं था, इसलिए किसी वर्कर में कोड लोड करने और स्क्रिप्ट लिखने के लिए एपीआई, सिंक्रोनस स्क्रिप्ट लोड करने के तरीके की तरह ही बना रहा. यह तरीका 2009 में आम था.

इतिहास: क्लासिक वर्कर

वर्कर कंस्ट्रक्टर, क्लासिक स्क्रिप्ट यूआरएल लेता है, जो दस्तावेज़ के यूआरएल के जैसा होता है. यह तुरंत नए वर्कर इंस्टेंस का रेफ़रंस देता है, जिससे मैसेजिंग इंटरफ़ेस दिखता है. साथ ही, terminate() वाला तरीका भी दिखता है, जो वर्कर को तुरंत बंद करके मिटा देता है.

const worker = new Worker('worker.js');

अतिरिक्त कोड लोड करने के लिए वेब वर्कर के अंदर importScripts() फ़ंक्शन उपलब्ध होता है. हालांकि, यह हर स्क्रिप्ट को फ़ेच और उसका आकलन करने के लिए, वर्कर को चलाने से रोकता है. यह क्लासिक <script> टैग की तरह ग्लोबल स्कोप में भी स्क्रिप्ट चलाता है. इसका मतलब है कि एक स्क्रिप्ट के वैरिएबल को दूसरी स्क्रिप्ट के वैरिएबल से ओवरराइट किया जा सकता है.

worker.js:

importScripts('greet.js');
// ^ could block for seconds
addEventListener('message', e => {
  postMessage(sayHello());
});

greet.js:

// global to the whole worker
function sayHello() {
  return 'world';
}

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

मॉड्यूल वर्कर डालें

Chrome 80 में शिपिंग करने वाले वेब वर्कर के लिए एक नया मोड है. इस मोड में JavaScript मॉड्यूल की मदद से एल्गोरिदम और परफ़ॉर्मेंस से जुड़े फ़ायदे मिलते हैं. इन्हें मॉड्यूल वर्कर कहा जाता है. अब Worker कंस्ट्रक्टर, {type:"module"} का नया विकल्प स्वीकार करता है. यह <script type="module"> के हिसाब से स्क्रिप्ट लोड करने और एक्ज़ीक्यूट करने के तरीके में बदलाव करता है.

const worker = new Worker('worker.js', {
  type: 'module'
});

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

JavaScript मॉड्यूल का इस्तेमाल करने पर, डाइनैमिक इंपोर्ट का इस्तेमाल भी किया जा सकता है. यह सुविधा लेज़ी लोडिंग कोड के लिए काम करती है. इससे, वर्कर के काम करने पर रोक नहीं लगती. डिपेंडेंसी लोड करने के लिए importScripts() का इस्तेमाल करने के मुकाबले, डाइनैमिक इंपोर्ट ज़्यादा बेहतर तरीके से काम करता है. ऐसा इसलिए, क्योंकि इंपोर्ट किए गए मॉड्यूल का एक्सपोर्ट, ग्लोबल वैरिएबल के इस्तेमाल के बजाय नतीजे के तौर पर दिखता है.

worker.js:

import { sayHello } from './greet.js';
addEventListener('message', e => {
  postMessage(sayHello());
});

greet.js:

import greetings from './data.js';
export function sayHello() {
  return greetings.hello;
}

बेहतरीन परफ़ॉर्मेंस देने के लिए, importScripts() वाला पुराना तरीका, मॉड्यूल वर्कर में उपलब्ध नहीं है. वर्कर को JavaScript मॉड्यूल इस्तेमाल करने के लिए स्विच करने का मतलब है कि पूरा कोड स्ट्रिक्ट मोड में लोड होता है. एक और अहम बदलाव यह है कि JavaScript मॉड्यूल के टॉप-लेवल स्कोप में this की वैल्यू undefined होती है, जबकि क्लासिक वर्कर में, वर्कर का ग्लोबल स्कोप होता है. अच्छी बात यह है कि ग्लोबल स्कोप की जानकारी के लिए, हमेशा से self ग्लोबल रहा है. यह सर्विस वर्कर के साथ-साथ डीओएम में सभी तरह के वर्कर के लिए उपलब्ध है.

modulepreload की मदद से, कर्मचारियों को पहले से लोड करें

मॉड्यूल वर्कर के साथ मिलने वाली परफ़ॉर्मेंस में एक बड़ा सुधार, कर्मचारियों और उनकी डिपेंडेंसी को पहले से लोड करने की क्षमता है. मॉड्यूल वर्कर के साथ, स्क्रिप्ट को स्टैंडर्ड JavaScript मॉड्यूल के तौर पर लोड और एक्ज़ीक्यूट किया जाता है. इसका मतलब है कि modulepreload का इस्तेमाल करके उन्हें पहले से लोड किया जा सकता है और यहां तक कि पहले से पार्स भी किया जा सकता है:

<!-- preloads worker.js and its dependencies: -->
<link rel="modulepreload" href="worker.js">

<script>
  addEventListener('load', () => {
    // our worker code is likely already parsed and ready to execute!
    const worker = new Worker('worker.js', { type: 'module' });
  });
</script>

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

पहले, वेब वर्कर स्क्रिप्ट को पहले से लोड करने के लिए उपलब्ध विकल्प सीमित थे और ज़रूरत के मुताबिक भरोसेमंद नहीं थे. क्लासिक वर्कर के पास पहले से लोड करने के लिए अपने खुद के "कर्मी" संसाधन प्रकार थे, लेकिन किसी भी ब्राउज़र ने <link rel="preload" as="worker"> को लागू नहीं किया. इस वजह से, पेजों को पहले से लोड करने वाले वेब वर्कर के लिए उपलब्ध मुख्य तकनीक <link rel="prefetch"> का इस्तेमाल थी, जो पूरी तरह से एचटीटीपी कैश पर निर्भर थी. सही कैशिंग हेडर के साथ इस्तेमाल करने पर, कर्मचारी स्क्रिप्ट को डाउनलोड करने के लिए इंतज़ार करने की ज़रूरत से बचने के लिए यह संभव हो गया. हालांकि, modulepreload के उलट, इस तकनीक में प्री-लोडिंग डिपेंडेंसी या प्री-पार्सिंग काम नहीं करती.

शेयर किए गए कर्मचारियों का क्या होगा?

Chrome 83 से, शेयर किए गए वर्कर को JavaScript मॉड्यूल पर काम करने के साथ अपडेट किया गया है. पूरी तरह से काम करने वाले वर्कर की तरह, {type:"module"} विकल्प के साथ शेयर किए गए वर्कर का बनाने पर, अब वर्कर स्क्रिप्ट, क्लासिक स्क्रिप्ट के बजाय मॉड्यूल के तौर पर लोड होती है:

const worker = new SharedWorker('/worker.js', {
  type: 'module'
});

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

सर्विस वर्कर के बारे में क्या ख्याल है?

JavaScript मॉड्यूल को एंट्री पॉइंट के तौर पर स्वीकार करने के लिए सर्विस वर्कर स्पेसिफ़िकेशन को पहले ही अपडेट किया जा चुका है. इसमें मॉड्यूल वर्कर वाले {type:"module"} विकल्प का इस्तेमाल किया जाता है. हालांकि, इस बदलाव को ब्राउज़र में अभी तक लागू नहीं किया गया है. ऐसा होने के बाद, नीचे दिए गए कोड का इस्तेमाल करके, JavaScript मॉड्यूल का इस्तेमाल करके सर्विस वर्कर को इंस्टैंशिएट किया जा सकता है:

navigator.serviceWorker.register('/sw.js', {
  type: 'module'
});

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

अतिरिक्त संसाधन और आगे का कॉन्टेंट