Потоки (streams): обзор

Как Node обрабатывает гигабайты данных, не загружая их целиком в память.

Поток (stream) — это способ обрабатывать данные по частям (кусками-чанками) по мере их поступления, а не целиком.

Проблема: файл больше памяти

Представьте файл лога на 5 ГБ. Если прочитать его через fs.readFile, Node попытается загрузить все 5 ГБ в оперативную память — и приложение упадёт. Потоки решают это: они подают данные кусками, и в памяти в каждый момент лежит лишь небольшой фрагмент.

Аналогия: чтобы перелить воду из бочки, не нужно поднимать всю бочку — достаточно открыть кран и пускать воду струёй.

Четыре типа потоков

ТипЧто делаетПример
Readableчитает данныечтение файла, тело запроса
Writableпишет данныезапись файла, ответ сервера
Duplexчитает и пишетсетевой сокет
Transformпреобразует на летусжатие, шифрование

Чтение потоком

Поток оповещает о событиях: data (пришёл очередной кусок), end (данные закончились), error (что-то сломалось):

const fs = require("fs");

const stream = fs.createReadStream("big.log", "utf8");

let chunks = 0;
stream.on("data", (chunk) => {
  chunks++;
  console.log("Получен кусок размером", chunk.length);
});
stream.on("end", () => {
  console.log("Готово, всего кусков:", chunks);
});
stream.on("error", (err) => {
  console.error("Ошибка:", err.message);
});

Память расходуется только на один кусок за раз, а не на весь файл.

pipe — соединяем потоки

Самая выразительная вещь — метод pipe: он «перенаправляет» данные из читающего потока в пишущий. Копирование файла потоком умещается в одну строку:

const fs = require("fs");

fs.createReadStream("source.txt")
  .pipe(fs.createWriteStream("copy.txt"));
// файл копируется кусками, память почти не тратится

Так же сервер может отдавать большой файл клиенту: читать его потоком и сразу «лить» в ответ через pipe, не держа целиком в памяти.

Обработка по кусочкам — это и есть суть

Идею «накапливать результат, обрабатывая поток частями» легко показать на чистом JS: считаем длину «потока» по приходящим кускам, не собирая их в одну строку:

// имитация кусков, приходящих из потока
const chunks = ["Привет", ", ", "поток", "ы", "!"];

let totalLength = 0;
for (const chunk of chunks) {
  totalLength += chunk.length; // обрабатываем кусок и забываем его
}

console.log("Кусков:", chunks.length);
console.log("Суммарная длина:", totalLength);

Вывод:

Кусков: 5
Суммарная длина: 15

Итог

  • Потоки обрабатывают данные по частям, не загружая всё в память.
  • Незаменимы для больших файлов и сетевых данных.
  • Четыре типа: Readable, Writable, Duplex, Transform.
  • pipe соединяет потоки — например, копирует файл одной строкой.
Проверьте себя
1. В чём главное преимущество потоков?
AОни шифруют данные
BОни обрабатывают данные по частям, не загружая всё в память
CОни работают быстрее на маленьких файлах
DОни не нуждаются в обработке ошибок
2. Что делает метод pipe?
AЗакрывает поток
BПеренаправляет данные из читающего потока в пишущий
CШифрует поток
DУдваивает скорость
3. Какое событие потока сигналит, что данные закончились?
Adata
Bend
Cclose
Dfinish
Поддержать проект