Потоки в деталях
Обрабатывайте данные по частям вместо загрузки всего объёма в память
- различать четыре типа потоков -- Readable, Writable, Duplex, Transform;
- читать Readable в режиме flowing, в режиме paused и через
for await; - соединять потоки с помощью
pipe()иpipeline(); - объяснять, что такое обратное давление (backpressure) и почему оно защищает память;
- писать собственный Transform для преобразования данных на лету.
1. Зачем нужны потоки
Представьте, что нужно скопировать видеофайл размером 4 ГБ. Наивный подход читает весь файл в переменную, а затем записывает эту переменную:
const data = fs.readFileSync('movie.mp4'); // 4 GB now lives in RAM
fs.writeFileSync('copy.mp4', data);
Для маленьких файлов это работает, но для больших -- катастрофа: процессу нужно 4 ГБ памяти только для хранения буфера, и ничего не происходит, пока чтение полностью не завершится. Потоки решают обе проблемы, перемещая данные порциями (chunks) (обычно по 64 КБ за раз). Память остаётся крошечной и постоянной, а работа начинается сразу с первой порции.
Аналогия. Подход без потоков -- это как осушать бассейн, перенося весь бассейн целиком на новое место. Поток -- это шланг: вода течёт непрерывно, в каждый момент вы держите не больше того, что помещается в шланге, и дальний конец начинает наполняться сразу.
Поскольку поток -- это интерфейс (согласованный набор методов и событий), тот же самый код, что читает файл, может читать из сетевого сокета или из декомпрессора. Именно эта сочетаемость и есть их настоящая сила.