Skip to main content

Потоки: ReadableStream і WritableStream

Потік (Stream) це послідовність даних, яка надходить поступово, а не вся одразу. Звичайний fetch().text() чекає, доки прийде все, і лише потім повертає результат, а потік (response.body) дозволяє обробляти дані частинами, одразу в міру надходження (chunk by chunk).

Теорія

TL;DR

  • Потік це дані, що надходять порціями (chunks), а не одним шматком.
  • ReadableStream це джерело, WritableStream це приймач, TransformStream це перетворювач.
  • response.body після fetch() це готовий ReadableStream.
  • reader.read() повертає проміс із { done, value }, де value це Uint8Array.
  • Потоки з'єднуються в ланцюжки через pipeThrough() і pipeTo().
  • Головна вигода: обробка великих обсягів без завантаження всього в пам'ять.

Швидкий приклад

javascript
const response = await fetch('https://example.com/large-file.txt'); // response.body це ReadableStream const reader = response.body.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; console.log('chunk size:', value.length); }

Що відбувається:

  1. fetch повертає Response, у якого .body це ReadableStream.
  2. Метод .getReader() створює читача, який віддає дані порціями (chunks).
  3. reader.read() повертає проміс із { done, value }:
    • value це шматок даних (тип Uint8Array);
    • done: true означає, що потік закінчився.

Так можна завантажувати гігабайти, не тримаючи весь файл в оперативній пам'яті.

Типи потоків у JavaScript

ТипПризначення
ReadableStreamджерело даних (читання)
WritableStreamприймач даних (запис)
TransformStreamпроміжна обробка (читання плюс запис)
DuplexStreamдвосторонній, наприклад сокет

Запис даних у WritableStream

javascript
const stream = new WritableStream({ write(chunk) { console.log('writing chunk:', chunk); }, close() { console.log('stream finished'); }, }); const writer = stream.getWriter(); await writer.write('Привіт'); await writer.write('Світ'); await writer.close();

Пояснення:

  • WritableStream приймає дані частинами через write(chunk);
  • close() викликається, коли потік завершено;
  • це зручно для відправлення даних, запису файлів, логування тощо.

TransformStream і перетворення на льоту

javascript
const upperCaseStream = new TransformStream({ transform(chunk, controller) { controller.enqueue(chunk.toUpperCase()); }, }); const writer = upperCaseStream.writable.getWriter(); const reader = upperCaseStream.readable.getReader(); await writer.write('hello '); await writer.write('world!'); await writer.close(); while (true) { const { done, value } = await reader.read(); if (done) break; console.log(value); // "HELLO " "WORLD!" }

TransformStream поєднує потік на вході (writable) і на виході (readable) та дозволяє перетворювати дані «на льоту».

З'єднання потоків через pipeTo і pipeThrough

Потоки можна з'єднувати між собою, як водопровідні труби:

javascript
await readableStream .pipeThrough(transformStream) .pipeTo(writableStream);

Тобто:

  • pipeThrough застосовує перетворення і повертає новий readable;
  • pipeTo передає результат у кінцевий приймач.

Практичний приклад із fetch() і TextDecoderStream:

javascript
const response = await fetch('https://example.com/data.txt'); const textStream = response.body .pipeThrough(new TextDecoderStream()) // декодуємо байти в текст .pipeThrough( new TransformStream({ transform(chunk, controller) { controller.enqueue(chunk.toUpperCase()); // перетворюємо текст }, }) ); const reader = textStream.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; console.log(value); }

Кожен шматок файлу спочатку перетворюється на текст, потім на верхній регістр, і одразу обробляється, без очікування кінця завантаження.

Потоки в Node.js і навіщо вони потрібні

У Node.js є власна реалізація (fs.createReadStream, stream.Writable та інші), але концепція та сама:

javascript
import fs from 'fs'; const readStream = fs.createReadStream('file.txt', { encoding: 'utf-8' }); readStream.on('data', chunk => console.log('Chunk:', chunk)); readStream.on('end', () => console.log('Done'));

Потоки Node.js побудовані на EventEmitter, а браузерні потоки на промісах і контролерах. Сучасний Node.js також підтримує Web Streams через node:stream/web, тому код можна переносити між середовищами.

ПеревагаОпис
ПродуктивністьНе треба чекати завантаження всього файлу
Економія пам'ятіОбробка йде «на льоту», порціями
КомпозиціяПотоки можна об'єднувати в ланцюжки
Двосторонній зв'язокВикористовуються у WebSocket, fetch(), Response, Body
КонтрольЧитання можна призупиняти та відновлювати (backpressure)

Проста аналогія: потоки це конвеєр. Дані не приходять одразу цілком, вони «їдуть» стрічкою порціями (chunks), і на кожному етапі (Readable, Transform, Writable) ти можеш їх обробляти, фільтрувати та зберігати.

ТипПризначенняПриклад використання
ReadableStreamДжерело данихfetch().body, file.stream()
WritableStreamПриймач данихЗапис у файл, лог, мережу
TransformStreamПеретворювачСтиснення, шифрування, фільтрація
pipeTo / pipeThroughЗ'єднання потоківreadable.pipeThrough(transform).pipeTo(writable)

Типові помилки

  • Читати response.body після response.text(). Тіло відповіді можна спожити лише один раз: після text(), json() або blob() потік уже заблокований. Якщо потрібні обидва варіанти, скористайтеся response.clone().
  • Забувати звільняти читача. Поки триває getReader(), потік залишається заблокованим. Викликайте reader.releaseLock() або обробляйте потік до кінця.
  • Ігнорувати backpressure. Якщо писати у WritableStream без await writer.write(...), черга розростається і перевага потоків над завантаженням у пам'ять зникає.
  • Вважати, що value це рядок. З response.body приходить Uint8Array; щоб отримати текст, потрібен TextDecoderStream або TextDecoder.
  • Не обробляти помилки в ланцюжку. pipeTo() повертає проміс, який може відхилитися; без catch помилка мережі залишиться непоміченою.
  • Плутати потоки Node.js і Web Streams. У них різні API: перші працюють через події data та end, другі через проміси та контролери.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.