स्ट्रीम—खास गाइड

Streams API की मदद से, पढ़ने, लिखने, और स्ट्रीम में बदलाव करने का तरीका जानें.

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

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

  • वीडियो इफ़ेक्ट: रीयल टाइम में इफ़ेक्ट लागू करने वाली ट्रांसफ़ॉर्म स्ट्रीम की मदद से, आसानी से पढ़ी जा सकने वाली वीडियो स्ट्रीम को सिंक करना.
  • डेटा (डि)कंप्रेस करना: फ़ाइल स्ट्रीम को ट्रांसफ़ॉर्म स्ट्रीम के ज़रिए पाइप करना, जो चुनिंदा तौर पर (डि)कंप्रेस करता है.
  • इमेज डिकोडिंग: एचटीटीपी रिस्पॉन्स स्ट्रीम को ट्रांसफ़ॉर्म स्ट्रीम के ज़रिए पिप करना. यह स्ट्रीम बाइट को बिटमैप डेटा में डिकोड करती है. इसके बाद, इसे बिटमैप का PNG में अनुवाद करने वाली किसी दूसरी ट्रांसफ़ॉर्म स्ट्रीम से करती है. अगर इसे किसी सेवा वर्कर के fetch हैंडलर में इंस्टॉल किया जाता है, तो इससे AVIF जैसे नए इमेज फ़ॉर्मैट को पारदर्शी तरीके से पॉलीफ़िल किया जा सकता है.

ब्राउज़र समर्थन

ReadableStream और WritableStream

ब्राउज़र के इस्तेमाल से जुड़ी सहायता

  • Chrome: 43.
  • Edge: 14.
  • Firefox: 65.
  • Safari: 10.1.

सोर्स

TransformStream

ब्राउज़र के इस्तेमाल से जुड़ी सहायता

  • Chrome: 67.
  • एज: 79.
  • Firefox: 102.
  • Safari: 14.1.

सोर्स

मुख्य कॉन्सेप्ट

अलग-अलग तरह की स्ट्रीम के बारे में बताने से पहले, हम आपको कुछ बुनियादी बातें बताना चाहते हैं.

चंक

चंक, डेटा का एक हिस्सा होता है. इसे स्ट्रीम में लिखा या उससे पढ़ा जाता है. यह किसी भी तरह का हो सकता है. स्ट्रीम में अलग-अलग तरह के चंक भी हो सकते हैं. ज़्यादातर मामलों में, कोई चंक किसी स्ट्रीम के लिए डेटा की सबसे छोटी यूनिट नहीं होगा. उदाहरण के लिए, किसी बाइट स्ट्रीम में एक बाइट के बजाय, 16 KiB Uint8Array यूनिट वाले चंक हो सकते हैं.

पढ़ी जा सकने वाली स्ट्रीम

पढ़ी जा सकने वाली स्ट्रीम, डेटा के उस सोर्स को दिखाती है जिसे पढ़ा जा सकता है. दूसरे शब्दों में, डेटा पढ़ने लायक स्ट्रीम से आता है. खास तौर पर, पढ़ी जा सकने वाली स्ट्रीम, ReadableStream क्लास का एक इंस्टेंस है.

लिखी जा सकने वाली स्ट्रीम

लिखने लायक स्ट्रीम, डेटा के लिए एक डेस्टिनेशन दिखाती है, जिसमें डेटा डाला जा सकता है. दूसरे शब्दों में, डेटा, लिखने के लिए उपलब्ध स्ट्रीम में जाता है. सीधे तौर पर, लिखने लायक स्ट्रीम, WritableStream क्लास का एक इंस्टेंस है.

स्ट्रीम को बदलें

ट्रांसफ़ॉर्म स्ट्रीम में स्ट्रीम का एक जोड़ा होता है: लिखने के लिए उपलब्ध स्ट्रीम, जिसे लिखने के लिए उपलब्ध साइड कहा जाता है और पढ़ने के लिए उपलब्ध स्ट्रीम, जिसे पढ़ने के लिए उपलब्ध साइड कहा जाता है. असल दुनिया में, इसका उदाहरण एक साथ कई भाषाओं में अनुवाद करने वाला व्यक्ति हो सकता है. ट्रांसफ़ॉर्म स्ट्रीम के हिसाब से, लिखने की अनुमति वाले हिस्से में डेटा लिखने पर, पढ़ने की अनुमति वाले हिस्से में नया डेटा उपलब्ध हो जाता है. सीधे तौर पर, writable प्रॉपर्टी और readable प्रॉपर्टी वाला कोई भी ऑब्जेक्ट, ट्रांसफ़ॉर्म स्ट्रीम के तौर पर काम कर सकता है. हालांकि, स्टैंडर्ड TransformStream क्लास की मदद से, आसानी से ऐसा पेयर बनाया जा सकता है जो सही तरीके से इंटरवॉवल हो.

पाइप चेन

स्ट्रीम का इस्तेमाल मुख्य तौर पर, एक-दूसरे से पाइप करके किया जाता है. पढ़ी जा सकने वाली स्ट्रीम को, पढ़ी जा सकने वाली स्ट्रीम के pipeTo() तरीके का इस्तेमाल करके, सीधे लिखी जा सकने वाली स्ट्रीम में पाइप किया जा सकता है. इसके अलावा, पढ़ी जा सकने वाली स्ट्रीम के pipeThrough() तरीके का इस्तेमाल करके, पहले एक या उससे ज़्यादा ट्रांसफ़ॉर्म स्ट्रीम में पाइप किया जा सकता है. इस तरह से एक साथ पाइप की गई स्ट्रीम के सेट को पाइप चेन कहा जाता है.

बैकप्रेशर

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

टीइंग

पढ़ी जा सकने वाली स्ट्रीम को tee() तरीके का इस्तेमाल करके, टी (अपरकेस 'T' के आकार के नाम पर) किया जा सकता है. इससे स्ट्रीम लॉक हो जाएगी. इसका मतलब है कि अब इसे सीधे तौर पर इस्तेमाल नहीं किया जा सकेगा. हालांकि, इससे दो नई स्ट्रीम बन जाएंगी, जिन्हें शाखाएं कहा जाता है. इनका इस्तेमाल अलग-अलग किया जा सकता है. स्ट्रीम को फिर से शुरू या पीछे नहीं किया जा सकता. इसलिए, स्ट्रीम शुरू करने का समय तय करना ज़रूरी है. इस बारे में ज़्यादा जानकारी बाद में दी जाएगी.

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

रीडबल स्ट्रीम के काम करने का तरीका

पढ़ी जा सकने वाली स्ट्रीम, एक ऐसा डेटा सोर्स होता है जिसे JavaScript में ReadableStream ऑब्जेक्ट से दिखाया जाता है. यह ऑब्जेक्ट, किसी मौजूदा सोर्स से फ़्लो करता है. ReadableStream() के कंस्ट्रक्टर से, दिए गए हैंडलर से एक ऐसा स्ट्रीम ऑब्जेक्ट बनता है जिसे पढ़ा जा सकता है. बुनियादी सोर्स दो तरह के होते हैं:

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

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

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

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

इस संदर्भ में अगले कंस्ट्रक्ट को कंट्रोलर कहा जाता है. आसानी से पढ़ी जा सकने वाली हर स्ट्रीम में एक कंट्रोलर होता है, जो नाम के मुताबिक आपको स्ट्रीम को कंट्रोल करने की सुविधा देता है.

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

ऐसी स्ट्रीम बनाना जिसे आसानी से पढ़ा जा सके

इसके कन्स्ट्रक्टर को कॉल करके, पढ़ी जा सकने वाली स्ट्रीम बनाई जाती है ReadableStream(). कन्स्ट्रक्टर में एक वैकल्पिक आर्ग्युमेंट underlyingSource होता है, जो ऐसे ऑब्जेक्ट को दिखाता है जिसमें ऐसे मेथड और प्रॉपर्टी होती हैं जिनसे यह तय होता है कि कन्स्ट्रक्ट किया गया स्ट्रीम इंस्टेंस कैसे काम करेगा.

underlyingSource

इसके लिए, डेवलपर के बताए गए इन वैकल्पिक तरीकों का इस्तेमाल किया जा सकता है:

  • start(controller): ऑब्जेक्ट बनने के तुरंत बाद कॉल किया जाता है. इस तरीके से स्ट्रीम सोर्स को ऐक्सेस किया जा सकता है. साथ ही, स्ट्रीम की सुविधा को सेट अप करने के लिए ज़रूरी अन्य काम भी किए जा सकते हैं. अगर यह प्रोसेस एसिंक्रोनस तरीके से की जानी है, तो यह तरीका, काम पूरा होने या न होने का संकेत देने के लिए, एक प्रॉमिस दिखा सकता है. इस मेथड में पास किया गया controller पैरामीटर, एक ReadableStreamDefaultController है.
  • pull(controller): ज़्यादा चंक फ़ेच होने पर, स्ट्रीम को कंट्रोल करने के लिए इसका इस्तेमाल किया जा सकता है. इसे तब तक बार-बार कॉल किया जाता है, जब तक कि स्ट्रीम के चंक की इंटरनल कतार पूरी न हो जाए. यह तब तक होता है, जब तक कि कतार अपने हाई वॉटर मार्क तक न पहुंच जाए. अगर pull() को कॉल करने पर कोई वादा मिलता है, तो pull() को तब तक फिर से कॉल नहीं किया जाएगा, जब तक कि वह वादा पूरा नहीं हो जाता. अगर प्रॉमिस अस्वीकार कर दिया जाता है, तो स्ट्रीम में गड़बड़ी हो जाएगी.
  • cancel(reason): जब स्ट्रीम कंज्यूमर स्ट्रीम को रद्द करता है, तब इसे कॉल किया जाता है.
const readableStream = new ReadableStream({
  start(controller) {
    /* … */
  },

  pull(controller) {
    /* … */
  },

  cancel(reason) {
    /* … */
  },
});

ReadableStreamDefaultController में ये तरीके इस्तेमाल किए जा सकते हैं:

  • ReadableStreamDefaultController.close(), इससे जुड़ी स्ट्रीम बंद कर देता है.
  • ReadableStreamDefaultController.enqueue() इससे, किसी चैनल से जुड़ी स्ट्रीम में कोई चंक डाला जाता है.
  • ReadableStreamDefaultController.error() इस वजह से, उस स्ट्रीम से आने वाले समय में होने वाले किसी भी इंटरैक्शन में गड़बड़ी आ सकती है.
/* … */
start(controller) {
  controller.enqueue('The first chunk!');
},
/* … */

queuingStrategy

दूसरा, ReadableStream() कंस्ट्रक्टर का आर्ग्युमेंट, queuingStrategy है. हालांकि, ऐसा करना ज़रूरी नहीं है. यह एक ऐसा ऑब्जेक्ट है जो स्ट्रीम के लिए, सूची में जोड़ने की रणनीति तय करता है. हालांकि, ऐसा करना ज़रूरी नहीं है. इसमें दो पैरामीटर होते हैं:

  • highWaterMark: यह कोई ऐसी संख्या होती है जो शून्य से बड़ी हो. इससे, सूची में शामिल करने की इस रणनीति का इस्तेमाल करके, स्ट्रीम के हाई वॉटर मार्क का पता चलता है.
  • size(chunk): यह एक फ़ंक्शन है, जो किसी चंक की वैल्यू का साइज़ कैलकुलेट करता है और उसे दिखाता है. यह साइज़ नॉन-नेगेटिव होता है. इस नतीजे का इस्तेमाल, बैकप्रेशर तय करने के लिए किया जाता है. यह नतीजा, सही ReadableStreamDefaultController.desiredSize प्रॉपर्टी के ज़रिए दिखता है. इससे यह भी तय होता है कि सोर्स के pull() मेथड को कब कॉल किया जाए.
const readableStream = new ReadableStream({
    /* … */
  },
  {
    highWaterMark: 10,
    size(chunk) {
      return chunk.length;
    },
  },
);

getReader() और read() तरीके

किसी पढ़ने लायक स्ट्रीम से पढ़ने के लिए, आपको एक रीडर की ज़रूरत होगी. यह एक ReadableStreamDefaultReader होगा. ReadableStream इंटरफ़ेस का getReader() तरीका, एक रीडर बनाता है और स्ट्रीम को उस पर लॉक करता है. स्ट्रीम के लॉक होने पर, इस स्ट्रीम को रिलीज़ करने से पहले, किसी और पाठक के साथ इसका इस्तेमाल नहीं किया जा सकता.

ReadableStreamDefaultReader इंटरफ़ेस का read() तरीका, स्ट्रीम की अंदरूनी सूची के अगले हिस्से का ऐक्सेस देने का वादा करता है. स्ट्रीम की स्थिति के आधार पर, यह नतीजे के साथ पूरा या अस्वीकार कर देता है. अलग-अलग संभावनाएं ये हैं:

  • अगर कोई चंक उपलब्ध है, तो वादा
    { value: chunk, done: false } फ़ॉर्मैट के ऑब्जेक्ट के साथ पूरा किया जाएगा.
  • अगर स्ट्रीम बंद हो जाती है, तो प्रॉमिस को
    { value: undefined, done: true } फ़ॉर्म के ऑब्जेक्ट के साथ पूरा किया जाएगा.
  • अगर स्ट्रीम में गड़बड़ी होती है, तो प्रॉमिस को गड़बड़ी की जानकारी के साथ अस्वीकार कर दिया जाएगा.
const reader = readableStream.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) {
    console.log('The stream is done.');
    break;
  }
  console.log('Just read a chunk:', value);
}

locked प्रॉपर्टी

किसी स्ट्रीम को पढ़ा जा सकता है या नहीं, यह देखने के लिए उसकी ReadableStream.locked प्रॉपर्टी को ऐक्सेस करें.

const locked = readableStream.locked;
console.log(`The stream is ${locked ? 'indeed' : 'not'} locked.`);

पढ़े जा सकने वाले स्ट्रीम कोड के सैंपल

नीचे दिया गया कोड सैंपल, सभी चरणों को दिखाता है. सबसे पहले, एक ReadableStream बनाएं, जो अपने underlyingSource आर्ग्युमेंट (यानी TimestampSource क्लास) में start() तरीका तय करता है. इस तरीके से, स्ट्रीम के controller को enqueue() 10 सेकंड के दौरान हर सेकंड में एक टाइमस्टैंप जोड़ने के लिए कहा जाता है. आखिर में, यह कंट्रोलर को स्ट्रीम close() करने के लिए कहता है. इस स्ट्रीम का इस्तेमाल करने के लिए, getReader() तरीके से रीडर बनाएं और read() को तब तक कॉल करें, जब तक स्ट्रीम done न हो जाए.

class TimestampSource {
  #interval

  start(controller) {
    this.#interval = setInterval(() => {
      const string = new Date().toLocaleTimeString();
      // Add the string to the stream.
      controller.enqueue(string);
      console.log(`Enqueued ${string}`);
    }, 1_000);

    setTimeout(() => {
      clearInterval(this.#interval);
      // Close the stream after 10s.
      controller.close();
    }, 10_000);
  }

  cancel() {
    // This is called if the reader cancels.
    clearInterval(this.#interval);
  }
}

const stream = new ReadableStream(new TimestampSource());

async function concatStringStream(stream) {
  let result = '';
  const reader = stream.getReader();
  while (true) {
    // The `read()` method returns a promise that
    // resolves when a value has been received.
    const { done, value } = await reader.read();
    // Result objects contain two properties:
    // `done`  - `true` if the stream has already given you all its data.
    // `value` - Some data. Always `undefined` when `done` is `true`.
    if (done) return result;
    result += value;
    console.log(`Read ${result.length} characters so far`);
    console.log(`Most recently read chunk: ${value}`);
  }
}
concatStringStream(stream).then((result) => console.log('Stream complete', result));

एसिंक्रोनस तरीके से दोहराना

हर read() लूप के दौरान यह जांचना कि स्ट्रीम done है या नहीं, शायद सबसे आसान एपीआई न हो. अच्छी बात यह है कि जल्द ही, ऐसा करने का एक बेहतर तरीका उपलब्ध होगा: एसिंक्रोनस तरीके से दोहराना.

for await (const chunk of stream) {
  console.log(chunk);
}

मौजूदा समय में, एसिंक्रोनस इटरेशन का इस्तेमाल करने का एक तरीका है पॉलीफ़िल के साथ बिहेवियर को लागू करना.

if (!ReadableStream.prototype[Symbol.asyncIterator]) {
  ReadableStream.prototype[Symbol.asyncIterator] = async function* () {
    const reader = this.getReader();
    try {
      while (true) {
        const {done, value} = await reader.read();
        if (done) {
          return;
          }
        yield value;
      }
    }
    finally {
      reader.releaseLock();
    }
  }
}

रीड-ऐबल स्ट्रीम बनाना

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

const readableStream = new ReadableStream({
  start(controller) {
    // Called by constructor.
    console.log('[start]');
    controller.enqueue('a');
    controller.enqueue('b');
    controller.enqueue('c');
  },
  pull(controller) {
    // Called `read()` when the controller's queue is empty.
    console.log('[pull]');
    controller.enqueue('d');
    controller.close();
  },
  cancel(reason) {
    // Called when the stream is canceled.
    console.log('[cancel]', reason);
  },
});

// Create two `ReadableStream`s.
const [streamA, streamB] = readableStream.tee();

// Read streamA iteratively one by one. Typically, you
// would not do it this way, but you certainly can.
const readerA = streamA.getReader();
console.log('[A]', await readerA.read()); //=> {value: "a", done: false}
console.log('[A]', await readerA.read()); //=> {value: "b", done: false}
console.log('[A]', await readerA.read()); //=> {value: "c", done: false}
console.log('[A]', await readerA.read()); //=> {value: "d", done: false}
console.log('[A]', await readerA.read()); //=> {value: undefined, done: true}

// Read streamB in a loop. This is the more common way
// to read data from the stream.
const readerB = streamB.getReader();
while (true) {
  const result = await readerB.read();
  if (result.done) break;
  console.log('[B]', result);
}

पढ़ी जा सकने वाली बाइट स्ट्रीम

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

पढ़ी जा सकने वाली बाइट स्ट्रीम बनाना

ReadableStream() कंस्ट्रक्टर में एक और type पैरामीटर पास करके, आसानी से पढ़ी जा सकने वाली बाइट स्ट्रीम बनाई जा सकती है.

new ReadableStream({ type: 'bytes' });

underlyingSource

पढ़ी जा सकने वाली बाइट स्ट्रीम के सोर्स को मैनिप्युलेट करने के लिए, ReadableByteStreamController दिया जाता है. इसका ReadableByteStreamController.enqueue() तरीका, chunk आर्ग्युमेंट लेता है जिसकी वैल्यू ArrayBufferView है. ReadableByteStreamController.byobRequest प्रॉपर्टी, मौजूदा BYOB पुल अनुरोध दिखाती है. अगर कोई अनुरोध नहीं है, तो यह शून्य दिखाती है. आखिर में, ReadableByteStreamController.desiredSize प्रॉपर्टी, कंट्रोल की गई स्ट्रीम की इंटरनल कतार को भरने के लिए, पसंद के साइज़ को दिखाती है.

queuingStrategy

ReadableStream() कंस्ट्रक्टर का दूसरा आर्ग्युमेंट queuingStrategy है, जो वैकल्पिक है. यह एक ऐसा ऑब्जेक्ट है जो स्ट्रीम के लिए, सूची में जोड़ने की रणनीति तय करता है. हालांकि, ऐसा करना ज़रूरी नहीं है. यह एक पैरामीटर लेता है:

  • highWaterMark: यह बाइट की ऐसी संख्या होती है जो शून्य से ज़्यादा हो. इससे, सूची में जोड़ने की इस रणनीति का इस्तेमाल करके, स्ट्रीम के हाई वॉटर मार्क का पता चलता है. इसका इस्तेमाल, बैकप्रेशर का पता लगाने के लिए किया जाता है. यह जानकारी, सही ReadableByteStreamController.desiredSize प्रॉपर्टी के ज़रिए दिखती है. इससे यह भी तय होता है कि सोर्स के pull() मेथड को कब कॉल किया जाए.

getReader() और read() तरीके

इसके बाद, mode पैरामीटर को इस हिसाब से सेट करके ReadableStreamBYOBReader का ऐक्सेस पाया जा सकता है: ReadableStream.getReader({ mode: "byob" }). इससे बफ़र की कॉपी मिटाने से बचने के लिए, बफ़र ऐलोकेशन को बेहतर तरीके से कंट्रोल करने में मदद मिलती है. बाइट स्ट्रीम से पढ़ने के लिए, आपको ReadableStreamBYOBReader.read(view) को कॉल करना होगा. यहां view एक ArrayBufferView है.

पढ़ने लायक बाइट के स्ट्रीम कोड का सैंपल

const reader = readableStream.getReader({ mode: "byob" });

let startingAB = new ArrayBuffer(1_024);
const buffer = await readInto(startingAB);
console.log("The first 1024 bytes, or less:", buffer);

async function readInto(buffer) {
  let offset = 0;

  while (offset < buffer.byteLength) {
    const { value: view, done } =
        await reader.read(new Uint8Array(buffer, offset, buffer.byteLength - offset));
    buffer = view.buffer;
    if (done) {
      break;
    }
    offset += view.byteLength;
  }

  return buffer;
}

नीचे दिया गया फ़ंक्शन, पढ़ने लायक बाइट स्ट्रीम दिखाता है, जो किसी भी तरीके से जनरेट किए गए अरे की शून्य-कॉपी रीडिंग की अनुमति देता है. यह पहले से तय किए गए 1,024 के चंक साइज़ का इस्तेमाल करने के बजाय, डेवलपर के दिए गए बफ़र को भरने की कोशिश करता है. इससे, डेवलपर को पूरा कंट्रोल मिलता है.

const DEFAULT_CHUNK_SIZE = 1_024;

function makeReadableByteStream() {
  return new ReadableStream({
    type: 'bytes',

    pull(controller) {
      // Even when the consumer is using the default reader,
      // the auto-allocation feature allocates a buffer and
      // passes it to us via `byobRequest`.
      const view = controller.byobRequest.view;
      view = crypto.getRandomValues(view);
      controller.byobRequest.respond(view.byteLength);
    },

    autoAllocateChunkSize: DEFAULT_CHUNK_SIZE,
  });
}

लिखने की स्ट्रीम की तकनीक

लिखने लायक स्ट्रीम वह डेस्टिनेशन है जिसमें डेटा लिखा जा सकता है. इसे JavaScript में WritableStream ऑब्जेक्ट की मदद से दिखाया जाता है. यह अंडरलाइंग सिंक के ऊपर एक एब्स्ट्रैक्शन के तौर पर काम करता है. यह एक निचले लेवल का I/O सिंक है, जिसमें रॉ डेटा लिखा जाता है.

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

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

इंटरनल लिस्ट में, उन हिस्सों का ट्रैक रखा जाता है जिन्हें स्ट्रीम पर लिखा गया है, लेकिन सिंक नहीं किया गया है.

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

फ़ाइनल कंस्ट्रक्ट को कंट्रोलर कहा जाता है. लिखने लायक हर स्ट्रीम में एक कंट्रोलर होता है, जो आपको स्ट्रीम को कंट्रोल करने की अनुमति देता है. जैसे, स्ट्रीम को रद्द करना.

लिखने के लिए उपलब्ध स्ट्रीम बनाना

Streams API का WritableStream इंटरफ़ेस, डेटा को किसी डेस्टिनेशन पर लिखने के लिए स्टैंडर्ड एब्स्ट्रैक्शन उपलब्ध कराता है. इसे सिंक कहा जाता है. इस ऑब्जेक्ट में पहले से मौजूद बैक प्रेशर और लाइन की सुविधा मौजूद है. लिखने के लिए उपलब्ध स्ट्रीम बनाने के लिए, उसके कंस्ट्रक्टर को कॉल करें WritableStream(). इसमें एक underlyingSink पैरामीटर होता है, जो एक ऑब्जेक्ट को दिखाता है. इसमें ऐसे तरीके और प्रॉपर्टी होती हैं जिनसे यह तय होता है कि बनाए गए स्ट्रीम इंस्टेंस का व्यवहार कैसा होगा.

underlyingSink

underlyingSink में, डेवलपर के तय किए गए ये वैकल्पिक तरीके शामिल किए जा सकते हैं. कुछ तरीकों में इस्तेमाल किया गया controller पैरामीटर, एक WritableStreamDefaultController है.

  • start(controller): ऑब्जेक्ट बनने के तुरंत बाद, इस तरीके को कॉल किया जाता है. इस तरीके के कॉन्टेंट का मकसद, सिंक की सुविधा का ऐक्सेस पाना होना चाहिए. अगर इस प्रोसेस को एसिंक्रोनस तरीके से करना है, तो यह सफलता या असफलता का संकेत देने के लिए एक प्रॉमिस दिखा सकता है.
  • write(chunk, controller): यह तरीका तब कॉल किया जाएगा, जब डेटा का नया डेटा (chunk पैरामीटर में बताया गया है) मौजूदा सिंक में लिखे जाने के लिए तैयार होगा. यह लिखने की प्रोसेस के पूरे होने या न होने का संकेत देने के लिए, एक प्रॉमिस दिखा सकता है. इस तरीके को तब ही कॉल किया जाएगा, जब पिछले लेख सफल हो गए होंगे. स्ट्रीम बंद या रद्द होने के बाद कभी भी ऐसा नहीं किया जाएगा.
  • close(controller): अगर ऐप्लिकेशन से यह सिग्नल मिलता है कि उसने स्ट्रीम में चंक लिखना बंद कर दिया है, तो इस तरीके को कॉल किया जाएगा. कॉन्टेंट में, सिंक में डेटा लिखने की प्रोसेस पूरी करने और उसका ऐक्सेस रिलीज़ करने के लिए, ज़रूरी कार्रवाई की जानी चाहिए. अगर यह प्रोसेस असिंक्रोनस है, तो यह सफलता या असफलता का संकेत देने के लिए, एक 'वादा' दिखा सकती है. यह तरीका सिर्फ़ तब कॉल किया जाएगा, जब पंक्ति में मौजूद सभी डेटा को लिखने की प्रोसेस पूरी हो जाएगी.
  • abort(reason): अगर ऐप्लिकेशन यह सिग्नल देता है कि उसे स्ट्रीम को अचानक बंद करना है और उसे गड़बड़ी वाली स्थिति में डालना है, तो इस तरीके को कॉल किया जाएगा. यह close() की तरह ही, होल्ड किए गए किसी भी संसाधन को हटा सकता है. हालांकि, लिखने की कार्रवाई के लिए लाइन में होने पर भी abort() को कॉल किया जाएगा. इन्हें बाहर फेंक दिया जाएगा. अगर यह प्रोसेस असिंक्रोनस है, तो यह सफलता या असफलता का सिग्नल देने के लिए, एक प्रॉमिस दिखा सकती है. reason पैरामीटर में DOMString होता है, जिसमें यह जानकारी होती है कि स्ट्रीम क्यों बंद की गई.
const writableStream = new WritableStream({
  start(controller) {
    /* … */
  },

  write(chunk, controller) {
    /* … */
  },

  close(controller) {
    /* … */
  },

  abort(reason) {
    /* … */
  },
});

Streams API का WritableStreamDefaultController इंटरफ़ेस, एक कंट्रोलर दिखाता है. इससे सेट अप के दौरान WritableStream की स्थिति को कंट्रोल किया जा सकता है. ऐसा तब किया जाता है, जब लिखने के लिए ज़्यादा चंक सबमिट किए जाते हैं या लिखने की प्रोसेस पूरी हो जाती है. WritableStream बनाते समय, सिंक को मैनिप्युलेट करने के लिए, उससे जुड़ा WritableStreamDefaultControllerइंस्टेंस दिया जाता है. WritableStreamDefaultController का सिर्फ़ एक तरीका है: WritableStreamDefaultController.error(), इस वजह से, उससे जुड़ी स्ट्रीम के साथ आगे होने वाले इंटरैक्शन में गड़बड़ी आ सकती है. WritableStreamDefaultController, signal प्रॉपर्टी के साथ भी काम करता है, जो AbortSignal का इंस्टेंस दिखाता है. इसकी मदद से, ज़रूरत पड़ने पर WritableStream की कार्रवाई रोकी जा सकती है.

/* … */
write(chunk, controller) {
  try {
    // Try to do something dangerous with `chunk`.
  } catch (error) {
    controller.error(error.message);
  }
},
/* … */

queuingStrategy

WritableStream() कंस्ट्रक्टर का दूसरा आर्ग्युमेंट queuingStrategy है, जो वैकल्पिक है. यह एक ऐसा ऑब्जेक्ट है जो स्ट्रीम के लिए, सूची में जोड़ने की रणनीति तय करता है. हालांकि, ऐसा करना ज़रूरी नहीं है. इसमें दो पैरामीटर होते हैं:

  • highWaterMark: यह कोई ऐसी संख्या होती है जो शून्य से बड़ी हो. इससे, सूची में शामिल करने की इस रणनीति का इस्तेमाल करके, स्ट्रीम के हाई वॉटर मार्क का पता चलता है.
  • size(chunk): यह एक फ़ंक्शन है, जो दिए गए चंक की वैल्यू का साइज़ कैलकुलेट करता है और उसे दिखाता है. यह साइज़ नॉन-नेगेटिव होता है. इस नतीजे का इस्तेमाल, बैक प्रेशर तय करने के लिए किया जाता है. साथ ही, इसे सही WritableStreamDefaultWriter.desiredSize प्रॉपर्टी के ज़रिए दिखाया जाता है.

getWriter() और write() तरीके

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

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

const writer = writableStream.getWriter();
const resultPromise = writer.write('The first chunk!');

locked प्रॉपर्टी

लिखने की अनुमति वाली स्ट्रीम को ऐक्सेस करके, यह देखा जा सकता है कि वह स्ट्रीम लॉक है या नहीं. इसके लिए, WritableStream.locked प्रॉपर्टी को ऐक्सेस करें.

const locked = writableStream.locked;
console.log(`The stream is ${locked ? 'indeed' : 'not'} locked.`);

Writable स्ट्रीम का कोड सैंपल

नीचे दिए गए कोड सैंपल में, सभी चरणों को दिखाया गया है.

const writableStream = new WritableStream({
  start(controller) {
    console.log('[start]');
  },
  async write(chunk, controller) {
    console.log('[write]', chunk);
    // Wait for next write.
    await new Promise((resolve) => setTimeout(() => {
      document.body.textContent += chunk;
      resolve();
    }, 1_000));
  },
  close(controller) {
    console.log('[close]');
  },
  abort(reason) {
    console.log('[abort]', reason);
  },
});

const writer = writableStream.getWriter();
const start = Date.now();
for (const char of 'abcdefghijklmnopqrstuvwxyz') {
  // Wait to add to the write queue.
  await writer.ready;
  console.log('[ready]', Date.now() - start, 'ms');
  // The Promise is resolved after the write finishes.
  writer.write(char);
}
await writer.close();

पढ़ी जा सकने वाली स्ट्रीम को लिखी जा सकने वाली स्ट्रीम में पिप करना

पढ़ी जा सकने वाली स्ट्रीम को, लिखी जा सकने वाली स्ट्रीम में भेजा जा सकता है. इसके लिए, पढ़ी जा सकने वाली स्ट्रीम के pipeTo() तरीके का इस्तेमाल करें. ReadableStream.pipeTo(), मौजूदा ReadableStream को दिए गए WritableStream से कनेक्ट करता है और ऐसा प्रॉमिस देता है जो पाइपिंग की प्रोसेस पूरी होने पर पूरा होता है. इसके अलावा, कोई गड़बड़ी होने पर वह प्रॉमिस भी अस्वीकार कर देता है.

const readableStream = new ReadableStream({
  start(controller) {
    // Called by constructor.
    console.log('[start readable]');
    controller.enqueue('a');
    controller.enqueue('b');
    controller.enqueue('c');
  },
  pull(controller) {
    // Called when controller's queue is empty.
    console.log('[pull]');
    controller.enqueue('d');
    controller.close();
  },
  cancel(reason) {
    // Called when the stream is canceled.
    console.log('[cancel]', reason);
  },
});

const writableStream = new WritableStream({
  start(controller) {
    // Called by constructor
    console.log('[start writable]');
  },
  async write(chunk, controller) {
    // Called upon writer.write()
    console.log('[write]', chunk);
    // Wait for next write.
    await new Promise((resolve) => setTimeout(() => {
      document.body.textContent += chunk;
      resolve();
    }, 1_000));
  },
  close(controller) {
    console.log('[close]');
  },
  abort(reason) {
    console.log('[abort]', reason);
  },
});

await readableStream.pipeTo(writableStream);
console.log('[finished]');

पूरी तरह बदलने वाली स्ट्रीम बनाना

Streams API का TransformStream इंटरफ़ेस, बदले जा सकने वाले डेटा का एक सेट दिखाता है. ट्रांसफ़ॉर्म स्ट्रीम बनाने के लिए, इसके कंस्ट्रक्टर TransformStream() को कॉल करें. यह दिए गए हैंडलर से ट्रांसफ़ॉर्म स्ट्रीम ऑब्जेक्ट बनाता है और उसे दिखाता है. TransformStream() कन्स्ट्रक्टर के पहले आर्ग्युमेंट के तौर पर, transformer को दिखाने वाला वैकल्पिक JavaScript ऑब्जेक्ट स्वीकार किया जाता है. ऐसे ऑब्जेक्ट में इनमें से कोई भी तरीका हो सकता है:

transformer

  • start(controller): ऑब्जेक्ट बनने के तुरंत बाद, इस तरीके को कॉल किया जाता है. आम तौर पर, इसका इस्तेमाल controller.enqueue() की मदद से, प्रीफ़िक्स वाले सेगमेंट को सूची में जोड़ने के लिए किया जाता है. ये हिस्से पढ़ने लायक हिस्से से पढ़े जाएंगे लेकिन लिखने के हिस्से में लिखे गए किसी भी हिस्से पर निर्भर नहीं होना चाहिए. अगर यह शुरुआती प्रोसेस असाइनोक्रोनस है, तो फ़ंक्शन, काम पूरा होने या न होने का संकेत देने के लिए एक प्रॉमिस दिखा सकता है. उदाहरण के लिए, प्रीफ़िक्स चंक हासिल करने में थोड़ी मेहनत लगती है. अस्वीकार किए गए प्रॉमिस से स्ट्रीम में गड़बड़ी होगी. कोई भी अपवाद होने पर, TransformStream() कन्स्ट्रक्टर उसे फिर से फेंक देगा.
  • transform(chunk, controller): इस तरीके को तब कॉल किया जाता है, जब मूल रूप से लिखा गया नया डेटा, बदलाव के लिए तैयार होता है. स्ट्रीम लागू करने से यह पक्का होता है कि इस फ़ंक्शन को सिर्फ़ पिछले ट्रांसफ़ॉर्म के पूरा हो जाने के बाद ही कॉल किया जाएगा. इसे start() के पूरा होने से पहले या flush() के कॉल होने के बाद कभी भी कॉल नहीं किया जाएगा. यह फ़ंक्शन, ट्रांसफ़ॉर्म स्ट्रीम का असल ट्रांसफ़ॉर्मेशन करता है. यह controller.enqueue() का इस्तेमाल करके, नतीजों को सूची में जोड़ सकती है. इससे, लिखने के लिए उपलब्ध साइड में लिखे गए एक चंक से, पढ़ने के लिए उपलब्ध साइड पर शून्य या एक से ज़्यादा चंक बन सकते हैं. यह इस बात पर निर्भर करता है कि controller.enqueue() को कितनी बार कॉल किया गया है. अगर ट्रांसफ़ॉर्म करने की प्रोसेस असाइनोक्रोनस है, तो यह फ़ंक्शन ट्रांसफ़ॉर्मेशन की सफलता या असफलता का संकेत देने के लिए, एक प्रॉमिस दिखा सकता है. अस्वीकार किए गए प्रॉमिस की वजह से, ट्रांसफ़ॉर्म स्ट्रीम के पढ़े और लिखे जा सकने वाले, दोनों हिस्सों में गड़बड़ी होगी. अगर कोई transform() तरीका नहीं दिया गया है, तो आइडेंटिटी ट्रांसफ़ॉर्म का इस्तेमाल किया जाता है. इससे पंक्तियों में जो सेगमेंट हैं उन्हें पढ़ने लायक हिस्से में नहीं बदला जाता.
  • flush(controller): इस तरीके को तब कॉल किया जाता है, जब लिखने के लिए उपलब्ध साइड में लिखे गए सभी चंक, transform() से गुज़रकर बदले जा चुके हों और लिखने के लिए उपलब्ध साइड बंद होने वाली हो. आम तौर पर, इसका इस्तेमाल सफ़िक्स के हिस्से को पढ़ने लायक हिस्से में जोड़ने के लिए किया जाता है, ताकि पहले ही उसे बंद कर दिया जाए. अगर डेटा मिटाने की प्रोसेस एसिंक्रोनस है, तो फ़ंक्शन सिग्नल के सही होने या न होने का प्रॉमिस लौटा सकता है. इसके नतीजे की जानकारी stream.writable.write() के कॉलर को दी जाएगी. इसके अलावा, अस्वीकार किए गए प्रॉमिस की वजह से, स्ट्रीम के पढ़े और लिखे जा सकने वाले, दोनों हिस्सों में गड़बड़ी होगी. किसी अपवाद को खारिज करने का मतलब है, अस्वीकार किए गए प्रॉमिस को दिखाना.
const transformStream = new TransformStream({
  start(controller) {
    /* … */
  },

  transform(chunk, controller) {
    /* … */
  },

  flush(controller) {
    /* … */
  },
});

सूची में शामिल करने के लिए, writableStrategy और readableStrategy रणनीतियां

TransformStream() कंस्ट्रक्टर के दूसरे और तीसरे वैकल्पिक पैरामीटर, वैकल्पिक writableStrategy और readableStrategy क्यूइंग स्ट्रेटजी हैं. इनके बारे में, readable और writable स्ट्रीम सेक्शन में बताया गया है.

स्ट्रीम को बदलने के लिए कोड का सैंपल

नीचे दिया गया कोड सैंपल, स्ट्रीम को बदलने का आसान तरीका दिखाता है.

// Note that `TextEncoderStream` and `TextDecoderStream` exist now.
// This example shows how you would have done it before.
const textEncoderStream = new TransformStream({
  transform(chunk, controller) {
    console.log('[transform]', chunk);
    controller.enqueue(new TextEncoder().encode(chunk));
  },
  flush(controller) {
    console.log('[flush]');
    controller.terminate();
  },
});

(async () => {
  const readStream = textEncoderStream.readable;
  const writeStream = textEncoderStream.writable;

  const writer = writeStream.getWriter();
  for (const char of 'abc') {
    writer.write(char);
  }
  writer.close();

  const reader = readStream.getReader();
  for (let result = await reader.read(); !result.done; result = await reader.read()) {
    console.log('[value]', result.value);
  }
})();

पढ़ने लायक स्ट्रीम को ट्रांसफ़ॉर्म स्ट्रीम के ज़रिए पाइप करना

ReadableStream इंटरफ़ेस के pipeThrough() तरीके की मदद से, मौजूदा स्ट्रीम को आपस में जोड़ने के लिए, ट्रांसफ़ॉर्मेशन स्ट्रीम या पढ़ने लायक किसी अन्य जोड़े जा सकते हैं. किसी स्ट्रीम को पाइप करने से, वह आम तौर पर पाइप की अवधि तक लॉक रहता है. इससे दूसरे पाठक उसे लॉक नहीं कर पाते.

const transformStream = new TransformStream({
  transform(chunk, controller) {
    console.log('[transform]', chunk);
    controller.enqueue(new TextEncoder().encode(chunk));
  },
  flush(controller) {
    console.log('[flush]');
    controller.terminate();
  },
});

const readableStream = new ReadableStream({
  start(controller) {
    // called by constructor
    console.log('[start]');
    controller.enqueue('a');
    controller.enqueue('b');
    controller.enqueue('c');
  },
  pull(controller) {
    // called read when controller's queue is empty
    console.log('[pull]');
    controller.enqueue('d');
    controller.close(); // or controller.error();
  },
  cancel(reason) {
    // called when rs.cancel(reason)
    console.log('[cancel]', reason);
  },
});

(async () => {
  const reader = readableStream.pipeThrough(transformStream).getReader();
  for (let result = await reader.read(); !result.done; result = await reader.read()) {
    console.log('[value]', result.value);
  }
})();

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

function upperCaseStream() {
  return new TransformStream({
    transform(chunk, controller) {
      controller.enqueue(chunk.toUpperCase());
    },
  });
}

function appendToDOMStream(el) {
  return new WritableStream({
    write(chunk) {
      el.append(chunk);
    }
  });
}

fetch('./lorem-ipsum.txt').then((response) =>
  response.body
    .pipeThrough(new TextDecoderStream())
    .pipeThrough(upperCaseStream())
    .pipeTo(appendToDOMStream(document.body))
);

डेमो

नीचे दिए गए डेमो में, पढ़ने, लिखने, और बदलाव करने की सुविधा वाली स्ट्रीम को काम करते हुए दिखाया गया है. इसमें pipeThrough() और pipeTo() पाइप चेन के उदाहरण भी शामिल हैं. साथ ही, tee() के बारे में भी बताया गया है. आपके पास डेमो को उसकी विंडो में चलाने या सोर्स कोड को देखने का विकल्प होता है.

ब्राउज़र में उपलब्ध काम की स्ट्रीम

ब्राउज़र में कई काम की स्ट्रीम पहले से मौजूद होती हैं. किसी BLOB से आसानी से ReadableStream बनाया जा सकता है. Blob इंटरफ़ेस का stream() तरीका, एक ReadableStream दिखाता है. इसे पढ़ने पर, ब्लॉब में मौजूद डेटा दिखता है. यह भी याद रखें कि File ऑब्जेक्ट एक खास तरह का Blob होता है. इसे किसी भी कॉन्टेक्स्ट में इस्तेमाल किया जा सकता है, जैसा कि कोई ब्लॉब कर सकता है.

const readableStream = new Blob(['hello world'], { type: 'text/plain' }).stream();

TextDecoder.decode() और TextEncoder.encode() के स्ट्रीमिंग वैरिएंट को TextDecoderStream और TextEncoderStream कहा जाता है.

const response = await fetch('https://streams.spec.whatwg.org/');
const decodedStream = response.body.pipeThrough(new TextDecoderStream());

CompressionStream और DecompressionStream ट्रांसफ़ॉर्म स्ट्रीम की मदद से, किसी फ़ाइल को कंप्रेस या डीकंप्रेस करना आसान है. नीचे दिए गए कोड सैंपल में बताया गया है कि Streams स्पेसिफ़िकेशन को कैसे डाउनलोड किया जा सकता है, उसे ब्राउज़र में ही कैसे कंप्रेस (gzip) किया जा सकता है, और कंप्रेस की गई फ़ाइल को सीधे डिस्क पर कैसे लिखा जा सकता है.

const response = await fetch('https://streams.spec.whatwg.org/');
const readableStream = response.body;
const compressedStream = readableStream.pipeThrough(new CompressionStream('gzip'));

const fileHandle = await showSaveFilePicker();
const writableStream = await fileHandle.createWritable();
compressedStream.pipeTo(writableStream);

File System Access API FileSystemWritableFileStream और एक्सपेरिमेंट के तौर पर शुरू की गई fetch() अनुरोध की स्ट्रीम, जंगल में लिखी जा सकने वाली स्ट्रीम के उदाहरण हैं.

Serial API, पढ़ने और लिखने के लिए उपलब्ध स्ट्रीम, दोनों का ज़्यादा इस्तेमाल करता है.

// Prompt user to select any serial port.
const port = await navigator.serial.requestPort();
// Wait for the serial port to open.
await port.open({ baudRate: 9_600 });
const reader = port.readable.getReader();

// Listen to data coming from the serial device.
while (true) {
  const { value, done } = await reader.read();
  if (done) {
    // Allow the serial port to be closed later.
    reader.releaseLock();
    break;
  }
  // value is a Uint8Array.
  console.log(value);
}

// Write to the serial port.
const writer = port.writable.getWriter();
const data = new Uint8Array([104, 101, 108, 108, 111]); // hello
await writer.write(data);
// Allow the serial port to be closed later.
writer.releaseLock();

आखिर में, WebSocketStream एपीआई, स्ट्रीम को WebSocket API के साथ इंटिग्रेट करता है.

const wss = new WebSocketStream(WSS_URL);
const { readable, writable } = await wss.connection;
const reader = readable.getReader();
const writer = writable.getWriter();

while (true) {
  const { value, done } = await reader.read();
  if (done) {
    break;
  }
  const result = await process(value);
  await writer.write(result);
}

काम के संसाधन

धन्यवाद

इस लेख की समीक्षा जेक आर्चिबाल्ड, फ़्रैंसुआ बोफ़र्ट, सैम डटन, मैटियास ब्यूलेन्स, सुर्मा, जो मेडली, और ऐडम राइस ने की है. जेक आर्किबाल्ड की ब्लॉग पोस्ट से, मुझे स्ट्रीम को समझने में बहुत मदद मिली. कुछ कोड सैंपल, GitHub उपयोगकर्ता @bellbind के एक्सप्लोरेशन से प्रेरित हैं. साथ ही, लेख के कुछ हिस्से स्ट्रीम के बारे में MDN वेब दस्तावेज़ पर आधारित हैं. स्ट्रीम स्टैंडर्ड के लेखकों ने इस स्पेसिफ़िकेशन को लिखने में बहुत अच्छा काम किया है. Unsplash पर रयान लारा की हीरो इमेज.