Пошук уроків, статей та іншого контенту
Чому великі файли читають частинами, а не одразу цілком — readable/writable потоки та pipe.
readFile завантажує весь вміст файлу в пам'ять перед тим, як повернути результат — прийнятно для невеликого конфігураційного файлу, але для файлу в кілька гігабайт (відео, великий лог, датасет) це або вичерпає доступну пам'ять процесу, або змусить користувача чекати повного завантаження, перш ніж почати хоч якусь обробку.
Stream — абстракція для роботи з даними послідовними невеликими частинами (chunks) у міру їх надходження, замість очікування всього обсягу одразу. Node.js має чотири типи потоків: Readable (джерело даних, наприклад читання файлу), Writable (приймач даних, наприклад запис у файл), Duplex (і те, і те, наприклад мережевий сокет) і Transform (перетворює дані на льоту, наприклад стиснення).
import { createReadStream, createWriteStream } from "node:fs";
const readStream = createReadStream("./big-file.log");
readStream.on("data", (chunk) => {
console.log(`Отримано частину: ${chunk.length} байтів`);
});
readStream.on("end", () => {
console.log("Читання завершено");
});
readStream.on("error", (error) => {
console.error("Помилка потоку:", error);
});Найпоширеніший патерн — з'єднати Readable-потік напряму з Writable-потоком методом pipe(), щоб дані передавались автоматично, частина за частиною, без ручного слухання подій data/end:
import { createReadStream, createWriteStream } from "node:fs";
const source = createReadStream("./input.log");
const destination = createWriteStream("./output.log");
source.pipe(destination); // копіює файл частинами, не завантажуючи весь у пам'ять одразуЯкщо джерело постачає дані швидше, ніж приймач встигає їх записувати (наприклад, швидкий диск і повільна мережа), потрібен механізм «притримати» джерело — це і називають backpressure. Метод pipe() враховує це автоматично; при ручній роботі через слухач події data цю логіку довелось би реалізовувати самостійно, ризикуючи неконтрольованим зростанням пам'яті в буфері.
Об'єкт відповіді HTTP-сервера (розглянутий в уроці про HTTP-сервер) сам є Writable-потоком — файл можна віддати клієнту напряму через pipe, не завантажуючи його повністю в пам'ять сервера:
import { createServer } from "node:http";
import { createReadStream } from "node:fs";
createServer((req, res) => {
createReadStream("./large-video.mp4").pipe(res); // потокова передача файлу клієнту
}).listen(3000);Завантажувати весь великий файл через readFile там, де достатньо потокової обробки — призводить до непотрібного піку споживання пам'яті й затримки до першого байта результату.
Ручна обробка події data без урахування backpressure замість pipe() — при швидкому джерелі й повільному приймачі буфер даних у пам'яті може необмежено зростати.
Забути обробник помилки ('error') на потоці — на відміну від звичайних Promise, необроблена помилка потоку не «спливає» автоматично через try/catch навколо коду створення потоку.
Потоки дозволяють обробляти дані частинами в міру надходження, замість очікування повного обсягу в пам'яті, — критично для великих файлів чи мережевих даних. pipe() з'єднує Readable і Writable потоки, автоматично враховуючи backpressure, і є стандартним, безпечнішим способом передавати дані між потоками порівняно з ручним слуханням події data.