До этого момента мы работали с потоками на чтение и запись, которые реализованы через стандартный модуль для работы с файловой системой fs.
Однако в Node.js есть отдельный модуль для работы с потоками — stream.
Duplex-поток реализует интерфейсы потока на чтение и потока на запись. Такие потоки используются:
● в TCP-сокетах — транспортный протокол передачи данных по сети;
● в потоках сжатия данных, шифрования данных и т. д.
Мы рассмотрим один из видов потока на чтение и запись (и самый распространённый) — поток Transform. Он предназначен для преобразования порции считанных или полученных данных и отправки их дальше по цепочке.
Допустим, в одном файле мы храним неизменные, необработанные логи в том виде, в котором их получаем. Но эти данные требуются нам и в обработанном виде. Для решения такой задачи вполне подойдёт поток Transform.
Разобьём задачу на шаги:
- Считать данные из файла.
- Преобразовать данные.
- Вывести данные в терминал.
Соединить все три шага мы можем посредством метода pipe(), который доступен в объекте любого потока. Этот метод предназначен для создания цепочек потоков и передачи по ней данных.
Читать данные из файла, используя потоки, мы уже умеем.
const fs = require('fs'); const readStream = new fs.ReadStream('./access.log', 'utf8'); |
Теперь создадим и настроим поток Transform.
const { Transform } = require('stream'); const transformStream = new Transform({ transform(chunk, encoding, callback) { const transformedChunk = chunk.toString().replace(/127.0.0.1/g, ''); this.push(transformedChunk); callback(); } }); |
Здесь мы создаём экземпляр класса Transfrom и передаём в конструктор метод transform. Этот метод и будет выполнять основную работу — преобразовывать данные и отправлять их дальше.
Допустим, нам надо удалить из логов любое упоминание об localhost. Делаем это посредством метода replace и простого регулярного выражения.
|
Вызовом метода this.push мы фиксируем изменения, а затем через вызов callback отправляем данные на выход потока.
Функция callback также фиксирует изменения и сразу отправляет данные на выход. Для этого надо передать в неё эти данные вторым аргументом. Первым аргументом она принимает объект ошибки.
callback(null, transformedChunk); |
Однако если фиксировать изменения методом this.push, то у нас появится возможность делать это несколько раз перед отправкой данных на выход. Это позволит, например, продублировать какую-то часть данных или изменять их поэтапно.
Теперь осталось вывести изменённые данные в терминал. Для этого используем вышеупомянутый метод pipe(). Соединяем поток на чтение с трансформирующим потоком, затем отправляем данные в поток на вывод process.stdout:
readStream.pipe(transformStream).pipe(process.stdout); |
Здесь появляется незнакомая нам сущность — process.stdout. Это интерфейс стандартного модуля process для работы с потоком вывода stdout. В нашем случае, отправляя данные в такой поток, мы выводим их в терминал. Кроме стандартного потока вывода, есть также стандартный поток ввода stdin и стандартный поток вывода ошибок stderr. Подробнее о потоках — в этой статье.
Итоговый код:
const fs = require('fs'); const readStream = new fs.ReadStream('./access.log', 'utf8'); const { Transform } = require('stream'); const transformStream = new Transform({ transform(chunk, encoding, callback) { const transformedChunk = chunk.toString().replace(/127.0.0.1/g, ''); this.push(transformedChunk); callback(); } }); readStream.pipe(transformStream).pipe(process.stdout); |
Итак, в этом уроке мы рассмотрели много важных понятий, касающихся не только разработки на Node.js, но и веб-разработки в целом. Работа с файлами, кодировки, двоичные данные, потоки — это лишь малая часть того интересного мира, который мы изучаем. Важно тренироваться и экспериментировать с пройденным материалом, пытаться адаптировать предоставленные знания под конкретные задачи. Только закрепляя теоретические знания на практике, можно сделать их устойчивой и неотъемлемой частью профессионализма.
|
Глоссарий
Буфер — участок памяти, в котором данные хранятся в ожидании обработки.
Класс Buffer — это глобальный класс, позволяющий работать с потоками двоичных данных.
Кодировка — это числовой код, который присваивается каждому символу (цифре, букве, знаку).
Потоки — это абстракция для «порционной» работы с данными, которые перемещаются из одного места в другое.
Файловый дескриптор — это неотрицательное целое число, которое возвращает ядро системы процессу при создании нового потока ввода-вывода.
Файловая система — это порядок, который определяет способ организации, хранения и именования данных на цифровых носителях информации.
Дополнительные материалы
- Официальная документация.
- Статья «Компьютерная память».
- Статья «О кодировках и кодовых страницах».
- Статья «И снова о Unicode».
- Класс Buffer в официальной документации.
- Флаги для работы с файловой системой.
- Статья «Что такое файловый дескриптор простыми словами».
- Статья «Стандартные потоки ввода-вывода».
Используемые источники
- Официальная документация.
- Статья «И снова о Unicode».
- Статья Do you want a better understanding of Buffer in Node.js? Check this out.