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

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

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

वेब प्लैटफ़ॉर्म पर, थ्रेडिंग और पैरलल प्रोसेसिंग के लिए मुख्य प्राइमटिव, Web Worker 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');
  }
});

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

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

Worker कन्स्ट्रक्टर में क्लासिक स्क्रिप्ट का यूआरएल डाला जाता है. यह यूआरएल, दस्तावेज़ के यूआरएल से जुड़ा होता है. यह तुरंत नए वर्कर्स इंस्टेंस का रेफ़रंस दिखाता है. इसमें मैसेजिंग इंटरफ़ेस के साथ-साथ 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';
}

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

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

JavaScript मॉड्यूल के एर्गोनॉमिक्स और परफ़ॉर्मेंस के फ़ायदों वाले वेब वर्कर के लिए, Chrome 80 में शिपिंग का एक नया मोड है. इसे मॉड्यूल वर्कर कहा जाता है. 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 मॉड्यूल का इस्तेमाल करने के लिए स्विच करने का मतलब है कि सारा कोड strict mode में लोड होता है. एक और ध्यान देने लायक बदलाव यह है कि JavaScript मॉड्यूल के टॉप-लेवल स्कोप में this की वैल्यू undefined है, जबकि क्लासिक वर्कर में वैल्यू, वर्कर का ग्लोबल स्कोप होती है. सौभाग्य से, self ग्लोबल हमेशा से ही मौजूद रहा है, जो ग्लोबल स्कोप का रेफ़रंस देता है. यह सर्विस वर्कर के साथ-साथ, सभी तरह के वर्कर और DOM में उपलब्ध है.

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 मॉड्यूल के लिए लागू करना होगा. साथ ही, अपडेट की जांच करते समय कुछ मामलों में, सेवा वर्कर को स्क्रिप्ट के लिए कैश मेमोरी को बायपास करना होगा.

अतिरिक्त संसाधन और आगे का लेख