Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Потоки: ReadableStream і WritableStream». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Потік (stream) це послідовність даних, яка надходить поступово, порціями (chunks), а не вся одразу.** `ReadableStream` це джерело даних (наприклад, `response.body` після `fetch`), `WritableStream` це приймач, а `TransformStream` перетворює дані «на льоту». Потоки з'єднуються в ланцюжок через `pipeThrough()` і `pipeTo()`, тому можна обробляти гігабайти, не завантажуючи все в оперативну пам'ять. ```javascript const response = await fetch('https://example.com/large-file.txt'); const reader = response.body.getReader(); const { done, value } = await reader.read(); // value це Uint8Array ``` **Ключове:** потоки дають обробку даних порціями, економію пам'яті та компонування через pipe замість очікування повного завантаження.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Потік (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`, другі через проміси та контролери.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.