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

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

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

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

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

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

ReadableStream और WritableStream

ब्राउज़र सहायता

  • 43
  • 14
  • 65
  • 10.1

सोर्स

TransformStream

ब्राउज़र सहायता

  • 67
  • 79
  • 102
  • 78 जीबी में से

सोर्स

मुख्य सिद्धांत

अलग-अलग तरह की स्ट्रीम के बारे में जानकारी देने से पहले, मुझे कुछ मुख्य कॉन्सेप्ट के बारे में बताना है.

थक्के

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

ऐसी स्ट्रीम जिन्हें पढ़ा जा सकता है

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

राइटेबल स्ट्रीम

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

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

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

पाइप चेन

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

बैकप्रेशर

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

टीइंग

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

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

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

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

  • पुश सोर्स से डेटा ऐक्सेस करने के बाद, आपको लगातार उस डेटा को ऐक्सेस किया जाता है. किसी स्ट्रीम का ऐक्सेस शुरू करना, रोकना या रद्द करना आपके ऊपर निर्भर करता है. उदाहरण के लिए, लाइव वीडियो स्ट्रीम, सर्वर से भेजे गए इवेंट या WebSockets.
  • पुल सोर्स के कनेक्ट होने पर, आपको उनसे डेटा के लिए साफ़ तौर पर अनुरोध करना होगा. उदाहरण के लिए, 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 में ये तरीके इस्तेमाल किए जा सकते हैं:

/* … */
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 को 10 सेकंड में हर सेकंड enqueue() टाइमस्टैंप दिया जाता है. आखिर में, इससे कंट्रोलर को स्ट्रीम 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));

एसिंक्रोनस इटरेशन

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

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();
    }
  }
}

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

tee() वाला तरीका, ReadableStream इंटरफ़ेस की मौजूदा रीड-ओनली स्ट्रीम की जानकारी देता है. यह दो एलिमेंट वाला कलेक्शन दिखाता है, जिसमें मिलने वाली दो ब्रांच, नए 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 का नया इंस्टेंस दिखाता है और स्ट्रीम को उस इंस्टेंस पर लॉक करता है. स्ट्रीम के लॉक होने पर, मौजूदा लेखक को रिलीज़ किए जाने तक कोई दूसरा लेखक हासिल नहीं किया जा सकता.

write() इंटरफ़ेस का WritableStreamDefaultWriter तरीका, डेटा के पास किए गए हिस्से को 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.`);

लिखने लायक स्ट्रीम कोड का सैंपल

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

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 की सूची बनाना ज़रूरी नहीं है. उन्हें पढ़ने लायक और लिखने लायक स्ट्रीम सेक्शन में बताया गया है.

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

नीचे दिया गया कोड सैंपल, एक आसान ट्रांसफ़ॉर्म स्ट्रीम को कार्रवाई में दिखाता है.

// 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() को दिखाया गया है. आपके पास डेमो को इसकी विंडो में चलाने का विकल्प होता है. इसके अलावा, सोर्स कोड भी देखा जा सकता है.

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

ब्राउज़र में, कई काम की स्ट्रीम पहले से मौजूद होती हैं. ब्लॉब से आसानी से 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 स्ट्रीम को क्रम से बदलने की सुविधा का इस्तेमाल करके, फ़ाइल को कंप्रेस या डीकंप्रेस किया जा सकता है. नीचे दिए गए कोड सैंपल में बताया गया है कि स्ट्रीम की खास बातों को कैसे डाउनलोड करें, उसे सीधे ब्राउज़र में कंप्रेस (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 के एक्सप्लोरेशन (विश्लेषण का तरीका) से प्रेरित हैं. साथ ही, प्रोज़ के कुछ हिस्सों को भी स्ट्रीम पर एमडीएन वेब दस्तावेज़ पर बनाया गया है. Streams Standard के लेखकों ने इस खास जानकारी को लिखने में शानदार काम किया है. Unsplash पर रायन लारा ने हीरो इमेज तैयार की है.