Прочитать 2 файла node js createreadstream

Обновлено: 05.07.2024

Доброго времени суток, друзья. Поговорим сегодня о работе с файлами в Node.js. Для работы с файлами используется модуль fs (сокращение от File System).

В этой статье мы рассмотрим следующие темы:

Для начала работы нам потребуется установленная Node.js. Подробную инструкцию по ее установке вы можете получить (тут).

Прежде чем начать работу с модулем, его следует импортировать в рабочем файле.

Пробежимся по основным методам этого модуля и посмотрим на примерах как с ними можно работать.

Работа с файловыми дескрипторами

Прежде чем двигаться дальше, давайте посмотрим на термины, о которых описано в статье ниже.

Файловый дескриптор — это неотрицательное целое число. Когда создается новый поток ввода-вывода, ядро возвращает процессу, создавшему поток ввода-вывода, его файловый дескриптор (Wikipedia).

Перейдем к примерам.

Ниже я приведу перечень флагов доступа к файлам

Следует помнить, что дескрипторы файлов необходимо закрывать для предотвращения переполнения памяти, а также проблем с производительностью. Выполним данную задачу с помощью метода close() и его синхронного аналога closeSync().

Работа с директориями

Для проверки существующей директории (файла) и доступов к нему в модуле fs применяется метод access

Вторым параметром устанавливается константа режима проверки:

Создание новой папки

Для создания каталогов присутствует асинхронный метод mkdir и синхронный mkdirSync.

Для создания в текущей директории нового каталога, перед путем следует указать переменную __dirname как вариант реализации абсолютного пути, либо воспользоваться метом resolve модуля path.

Чтение содержимого папки

Для получения содержимого директории используются два метода readdir и readdirSync. Первым параметром для методов передается путь директории, содержимое которой нужно получить.

Удаление папки

Удаление директории производится с помощью методов rmdir и rmdirSync. Первым параметром методов является путь удаляемой директории.

Получение системной информации о файле

Каждый файл, который мы загружаем, помимо данных хранит в себе системную информацию. Для получения этой информации можно воспользоваться методом stat() или выбрать синхронный вариант statSync()

Вся информация хранится в получаемом объекте stats. Данный объект хранит в себе методы для получения дополнительной полезной информации.

Перечислю некоторые из этих свойств:

stats.isDirectory() метод позволяет узнать, является ли файл директорией;

stats.isFile() метод возвращает true, если это файл;

stats.isSocket() метод возвращает true, если это сокет;

stats.isSymbolicLink() метод возвращает true, если файл является символьной ссылкой;

stats.size свойство, которое возвращает размер файла;

stats.birthtime возвращает время и дату, когда данный файл был создан.

Модуль path и путь к файлу Node.js

Основной проблемой при работе с файлами и папками в операционных системах является разный синтаксис написания путей их расположения. Для решения этой проблемы в Node.js есть модуль path c набором полезных методов.

Для началы работы с модулем его нужно импортировать.

Получение имени, пути и расширения файла

Предположим, что в папке /temp лежит файл template.txt. Воспользуемся методами модуля path для получения имени файла, пути к нему, а так же его расширения.

Метод basename возвращает наименование файла. Первым параметром передается путь к файлу, вторым параметром (опционально) передается расширение файла, если необходимо получить наименование файла без расширения.

path.basename(file, ‘.txt’) // tempalate

Метод dirname возвращает весь путь директории, где лежит файл и имеет один параметр, в который передается путь к файлу.

Метод extname возвращает расширение переданного файла.

Работа с путями файла

В модуле path есть ряд методов для удобного формирования путей к нужному файлу или директории. Мы не будем рассматривать все методы, а только наиболее часто используемые. Рассмотрим их ниже.

Метод join принимает список параметров, объединяет их в одну строку, используя разделитель, подходящий к конкретной операционной системе, в которой будет исполнятся код.

Метод resolve используется для нахождения абсолютных путей к файлу.

Метод normalize позволяет найти путь к файлу, используя синтаксис переходов (.. и .) по папкам.

Переименование файла или директории

Методы rename() и renameSync() первым параметром принимают путь к файлу, который нужно переименовать. Второй параметр отвечает за новое наименование файла.

Чтение файла

Если вторым параметром не указана кодировка, то возвращается Buffer. Эти методы полностью загружают файлы в память компьютера, что может сильно отразиться на производительности. Если размер файла большой, то стоит воспользоваться потоками fs.createReadStream()

Запись файла

Чтобы перезаписать контент файлов, используются методы writeFile и writeFileSync. Важный момент! Если файла, контент которого нужно перезаписать, не существует, то он будет создан автоматически.

Копирование файла

Методы copyFile() и copyFileSync() первым параметром принимают путь файла для копирования. Второй параметр принимает название пути и нового файла. Третьим параметром является колбэк функция, которая возвращает ошибку.

Удаление файла

Последнее, что мы рассмотрим в этой статье будут методы unlink() и unlinkSync(). Первым параметром методы принимают путь к удаляемому файлу. Второй параметр в методе unlink возвращает колбэк функцию с ошибкой.

Заключение

В данной статье мы разобрали работу Node.js с файлами, на примерах посмотрели основные полезные методы модулей fs и path. Исходный код вы сможете найти тут. Надеюсь данная статья была вам полезна. Учитесь, думайте, пишите код. Удачного кодинга, друзья!

Подписывайтесь на наш канал в Telegram и на YouTube для получения самой последней и актуальной информации.

Я думаю многие не раз слышали про Node js Streams, но так ни разу и не использовали, либо использовали, не задумываясь как же они работают, запайпили (pipe) стрим и норм. Давайте же разберемся что такое стримы, запайпить (pipe), чанки (chunk — часть данных) и все такое))

Итак, что же такое стрим(далее по тексту буду использовать вместо Stream (поток)). Стрим — это концепция, c помощью которой можно обрабатывать данные небольшими частями, что позволяет задействовать небольшой объем оперативной памяти. Также с ее помощью мы можем разбить обработку каждой части на независимые друг от друга модули (функции либо классы). Например, мы можем сразу сжать часть данных, потом зашифровать и записать в файл. Основная идея в том, чтобы не работать с данными целиком, а поочередно обрабатывать часть данных.

В Node js есть 4 вида стримов:

  • Readable — чтение
  • Writable — запись
  • Duplex — чтение и запись
  • Transform — вид Duplex потока, который может изменять данные

Простой пример

Думаю, что многие уже использовали стримы, даже не подозревая об этом. В этом примере мы просто отправляем клиенту файл.


Разница лишь в том, что в первом случае мы загружаем часть файла и отправляем ее, таким образом, не загружая оперативную память сервера. Во втором случае мы сразу загружаем файл целиком в оперативную память и только потом отправляем.

Далее в статье разберем каждый стрим по отдельности. Стрим можно создать используя наследование или с помощью функции-конструктора.


Во всех примерах я буду использовать 2 способ.

Readable stream

Давайте рассмотрим как же нам создать Readable стрим в NodeJS.


Как видим из примера выше, этот класс принимает набор параметров. Мы рассмотрим только те, которые нужны для общего понимания работы Readable стрима, остальные вы можете посмотреть в документации. Нас интересует параметр highWaterMark и метод _read.

highWaterMark — это максимальное количество байтов внутреннего буфера стрима (по умолчанию 16кб) по достижению которого считывание из ресурса приостанавливается. Для того, чтобы продолжить считывание, нам нужно освободить внутренний буфер. Мы можем это сделать вызвав методы pipe, resume или подписавшись на событие data.

_read — это реализация приватного метода, который вызывается внутренними методами класса Readable. Он вызывается постоянно пока размер данных не достигнет highWaterMark.

Ну и последний метод, который нас интересует, это readable.push, он непосредственно и добавляет данные во внутренний буфер. Он возвращает true, но как только буфер будет заполнен, то вызов этого метода начнет возвращать false. Он может управляться методом readable._read.

Давайте теперь посмотрим пример для прояснения ситуации.


Для начала скажу, что counter.read() — это не тот _read, который мы реализовали в классе. Тот метод является приватным, а этот — открытым, и он возвращает данные из внутреннего буфера. Когда мы выполним этот код, в консоли мы увидим следующее:


Что же тут произошло? При создании стрима new Counter(< highWaterMark: 2 >) мы указали, что размер нашего внутреннего буфера будет равняться 2-м байтам, т.е. может хранить 2 символа (1 символ = 1 байт). После вызова counter.read() стрим начинает считывание, записывает '1' во внутренний буфер и возвращает его. Затем он продолжает считывание, записывает '2'. Когда он запишет '3', то буфер будет заполнен, readable.push вернет false, и стрим будет ждать, пока внутренний буфер освободится. Т.к. в нашем примере нет логики на освобождения буфера, скрипт завершится.

Как и говорилось ранее, для того, чтобы чтение не прерывалось, нам нужно постоянно очищать внутренний буфер. Для этого мы подпишемся на событие data. Заменим последние 2 строчки следующим кодом.


Теперь если мы запустим этот пример, то увидим, что все сработало как надо и в консоли выведутся цифры от 1 до 1000.

Writable stream

На самом деле он очень похож на Readable стрим, только предназначен для записи данных.


Он принимает похожие параметры, как и Readable стрим. Нас интересуют highWaterMark и _write.

_write — это приватный метод, который вызывается внутренними методами класса Writable для записи порции данных. Он принимает 3 параметра: chunk (часть данных), encoding (кодировка, если chunk это строка), callback (функция, которая вызывается после успешной или неудачной записи).

highWaterMark — это максимальное количество байтов внутреннего буфера стрима (по умолчанию 16кб), по достижению которого stream.write начнет возвращать false.

Давайте перепишем предыдущий пример со счетчиком.


По сути все просто, но есть один интересный нюанс, о котором стоит помнить! При создании стрима new Counter(< highWaterMark: 2 >) мы указали, что размер нашего внутреннего буфера будет равняться 2-м байтам, т.е. может хранить 2 символа (1 символ = 1 байт). Когда же счетчик дойдет до десяти, то буфер будет заполняться при каждом вызове write, соответственно, если бы запись осуществлялась в медленный источник, то все остальные данные при вызове write сохранялись бы в оперативную память, что могло бы вызвать ее переполнение (в данном примере это конечно же не важно, так как наш буфер 2 байта, но вот с большими файлами об этом нужно помнить). Когда возникает такая ситуация, нам надо подождать, пока стрим запишет текущую порцию данных, освободит внутренний буфер (вызовет событие drain), и затем мы можем возобновить запись данных. Давайте перепишем наш пример.


Метод events.once был добавлен в v11.13.0 и позволяет создать промис и подождать, пока определенное событие выполнится один раз. В этом примере мы проверяем, возможна ли запись данных в стрим, если нет, то ожидаем, пока буфер освободится, и продолжаем запись.

На первый взгляд это может показаться ненужным действием, но при работе с большими объемами данных, например файлами, которые весят больше 10гб, забыв сделать это, вы можете столкнуться с утечкой памяти.

Duplex stream

Он объединяет в себе Readable и Writable стримы, то есть мы должны написать реализацию двух методов _read и _write.


Здесь нам интересны 2 параметра, которые мы можем передать в конструктор, это readableHighWaterMark и writableHighWaterMark, которые позволяют нам указать размер внутреннего буфера для Readable, Writable стримов соответственно. Вот так будет выглядеть реализация предыдущих двух примеров с помощью Duplex стрима.


Думаю, этот код не нуждается в пояснениях, так как он такой же, как и раньше, только в одном классе.

Transform stream

Этот стрим является Duplex стримом. Он нужен для преобразования порции данных и отправки дальше по цепочке. Его можно реализовать таким же способом, как и остальные стримы.


Нас интересует метод _transform.

_transform — это приватный метод, который вызывается внутренними методами класса Transform для преобразования порции данных. Он принимает 3 параметра: chunk (часть данных), encoding (кодировка, если chunk это строка), callback (функция, которая вызывается после успешной или неудачной записи).

stream.pipe — этот метод используется для соединения Readable стрима с Writable стримом, а также для создания цепочек стримов. Это значит, что мы можем считывать часть данных и передавать в следующий стрим для обработки, а потом в следующий и т д.

Давайте напишем Transform стрим, который будет добавлять символ * в начало и конец каждой части данных.


В этом примере я использовал Readable и Writable стримы из предыдущих примеров, а также добавил Transform. Как видим, получилось довольно просто.

Вот мы и рассмотрели как устроены стримы. Основная их концепция — это обработка данных по частям, что очень удобно и не требует расходов больших ресурсов. Также стримы можно использовать с итераторами, что делает их еще более удобным в использовании, но это уже совсем другая история.

Я копаюсь с node.js и обнаружил два способа чтения файла и отправки его по проводам, как только я установил, что он существует, и отправил правильный тип MIME с помощью writeHead:

Правильно ли я полагаю, что fs.createReadStream может обеспечить лучшее взаимодействие с пользователем, если рассматриваемый файл был чем-то большим, например видео? Такое ощущение, что это может быть меньше блокировать; Это правда? Есть ли другие плюсы, минусы, предостережения или ошибки, которые мне нужно знать?

4 ответа

Лучший подход, если вы просто собираете «data» для «write ()» и «close» для «end ()»:

Подход read.pipe(write) или sys.pump(read, write) также имеет преимущество добавления управления потоком. Таким образом, если поток записи не может принимать данные так же быстро, он скажет потоку чтения отойти назад, чтобы минимизировать объем данных, буферизуемых в памяти.

flags:"r" и mode:0666 подразумеваются тем фактом, что это FileReadStream . Кодировка binary устарела - если кодировка не указана, она будет работать только с буферами необработанных данных.

Кроме того, вы можете добавить некоторые другие полезности, которые сделают ваш файл более привлекательным:

  1. Отыщите req.headers.range и посмотрите, совпадает ли она со строкой типа /bytes=(1+)-(9+)/ . Если это так, вы хотите просто поток с этого начала до конца места. (Отсутствующее число означает 0 или «конец».)
  2. Хэшируйте индекс и время создания из вызова stat () в заголовок ETag. Если вы получили заголовок запроса с «if-none-match», совпадающим с этим заголовком, отправьте обратно 304 Not Modified .
  3. Проверьте заголовок if-modified-since на дату mtime объекта stat. 304, если он не был изменен с момента предоставления.

Кроме того, в общем случае, если можете, отправьте заголовок Content-Length . (Вы stat - файл, так что вы должны иметь это.)

Если это большой файл, тогда «readFile» перегружает память, поскольку он буферизует все содержимое файла в памяти и может повредить вашу систему. Пока ReadStream читается кусками.

Запустите этот код и наблюдайте за использованием памяти на вкладке производительности диспетчера задач.

Теперь вместо «readFile» используйте readStream и следите за использованием памяти.

Примечание: код взят из курса «Самера буна» по Pluralsight

Другая, возможно, не очень известная вещь, это то, что я считаю, что Node лучше очищает неиспользуемую память после использования fs.readFile по сравнению с fs.createReadStream . Вы должны проверить это, чтобы проверить, что работает лучше всего. Кроме того, я знаю, что с каждой новой версией Node это становилось лучше (то есть сборщик мусора становился умнее в таких ситуациях).

fs.readFile загрузит весь файл в память, как вы указали, а fs.createReadStream будет читать файл кусками указанного вами размера.

Клиент также начнет получать данные быстрее, используя fs.createReadStream , так как они отправляются кусками по мере их чтения, а fs.readFile будет считывать весь файл и только после этого начнет отправлять его клиенту. Это может быть незначительным, но может иметь значение, если файл очень большой, а диски медленные.

Подумайте об этом, хотя, если вы запустите эти две функции для файла размером 100 МБ, первая из них будет использовать 100 МБ памяти для загрузки файла, тогда как последняя будет использовать не более 4 КБ.

Потоки в Node.js обладают большой силой: в вашем распоряжении асинхронность в работе с вводом и выводом и вы можете преобразовывать данные в независимых этапах. В этом руководстве я расскажу вам о теории и научу, как использовать трансформаторы объектного потока в стиле Gulp.

Когда я находился в процессе написания моей книги FrontEnd инструментирование с Gulp, Bower и Yeoman, я решил не просто объяснять API и варианты использования этих инструментов, но также сосредоточиться на концепциях, лежащих в их основе.

Вы знаете, что в JavaScript, как нигде больше, инструменты и платформы приходят и уходят быстрее, чем вы сможете зарегистрировать домены и Github группы для них. Для Gulp.js одна из самых важных концепций — потоки!

Используя Gulp, вы хотите считывать входные файлы и преобразовывать их в желаемый результат, загружать множество файлов JavaScript и объединять их в один. API Gulp предоставляет ряд методов для чтения, преобразования и записи файлов, причем все они под капотом используют потоки.

Потоки — довольно старая концепция в вычислительной технике, возникшая с ранних дней Unix в 1960-х годах.

Поток представляет собой последовательность данных, поступающих во времени от источника и направляющихся к месту назначения. Типов источников множество: файлы, память компьютера или устройства ввода, такие как клавиатура или мышь.

Как только поток открывается, данные передаются блоками (chunks) из своего источника в процесс, потребляющий их. Поступая из файла, каждый символ или байт считывается по одному; поступая с клавиатуры, каждое нажатие клавиши передает данные потоком.

Самое большое преимущество по сравнению с одновременной загрузкой всех данных состоит в том, что теоретически входные данные могут быть бесконечными и без ограничений.

Например, возвращаясь к клавиатуре, в этом есть смысл: почему кто-то должен закрывать входной поток, который вы используете для управления своим компьютером?

Данные представляют собой последовательность элементов, поступающих во времени (например, символы или байты).

Читаемые потоки могут исходить из разных источников, таких как устройства ввода (клавиатура), файлы или данные, хранящиеся в памяти. Записываемые потоки также могут заканчиваться в разных местах, таких как файлы и память, а также в командной строке. Читаемые и записываемые потоки могут быть взаимозаменяемы: ввод с клавиатуры может оказаться в файле, ввод файла в командной строке.

Мало того, что возможно бесконечное количество входных данных, но вы также можете комбинировать различные читаемые и записываемые потоки. Данные нажатой клавиши могут быть напрямую сохранены в файл или вы можете вывести считываемый файл в командной строке или даже подключенном принтере. Интерфейс остается неизменным независимо от того, какие источники или места назначения.

Самая простая программа в Node.js, использующая потоки, — пробрасывание (piping) стандартного ввода нажатия клавиши клавиатуры в стандартный вывод (консоль):

Мы берем наш читаемый поток ( process.stdin ) и пробрасываем его в записываемый поток ( process.stdout ). Как было сказано ранее, мы можем передавать любой контент из любого читаемого источника в любое записываемое место назначения.

Вывод в консоли HTML-страницы может быть не очень полезен, но думайте об этом, как о пробрасывании данных в файл для веб-скрейпинга.

Потоки хороши не только для передачи данных между различными источниками и адресатами.

Когда после открытия потока данные становятся доступны, разработчики могут преобразовывать данные, поступающие из потока, до того, как они достигнут своего адресата, например, преобразовывая все символы нижнего регистра в файле в символы верхнего регистра.

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

Чтобы изменить данные, вы добавляете преобразующие блоки между входом и выходом.

В этом примере вы получаете входные данные из разных источников и передаете их через преобразующий toUpperCase . Это изменяет символы нижнего регистра на их эквивалент в верхнем регистре. Эти блоки могут быть созданы один раз и использоваться повторно для разных входов и выходов.

В следующем примере мы определяем функцию toUpperCase , которая. преобразует каждую букву в ее эквивалент в верхнем регистре. Есть много способов создать эту функциональность, но я всегда был большим поклонником потоковых пакетов Node.js, таких как through2 . Они предлагают хорошую оболочку для создания новых трансформаторов:

  1. Пакет through2 принимает функцию в качестве первого параметра. Эта функция принимает данные (в буфере), информацию о кодировании и функцию обратного вызова, которую мы можем вызвать, как только закончим наше преобразование.
  2. Обычно в потоках Node.js мы передаем Buffers с данными из потока. Исходя из process.stdin , это, скорее всего, текущая строка до того, как мы нажали Return. Исходя из файла, фактически это может быть что угодно. Мы преобразуем текущий буфер в строку, создаем прописную версию и снова конвертируем ее в буфер. Функция обратного вызова принимает два аргумента. Первый - возможная ошибка. Поток будет аварийно завершен и программа остановит выполнение, если для отлавливания ошибки нет слушателя события end . Передаем null , если нас это устраивает. Второй аргумент - преобразованные данные.
  3. Мы можем использовать этот трансформатор и прокинуть в него входные данные от читаемого потока. Преобразованные данные пересылаются в наш записываемый поток.

Это полностью в духе функционального программирования. Мы можем использовать и переиспользовать один и тот же трансформатор для любого другого ввода или вывода, если он поступает из читаемого потока. Нам не важен входной источник или адресат. Кроме того, мы не ограничены одним трансформатором. Мы можем связать (chain) столько трансформаторов, сколько пожелаем:

Если вы работали с Gulp, приведенный выше код должен казаться вам знакомым. Очень похоже, не правда ли? Однако потоки Gulp различаются в одном специфичном вопросе: мы не передаем данные в буфере, мы используем старые добрые JavaScript объекты.

Обычно в стандартных потоках файл рассматривается как возможный входящий источник реальных данных, который необходимо обработать. Вся информация о происхождении, подобно пути или имени файла, теряется после открытия потока.

В Gulp вы работаете не только с содержимым одного или нескольких файлов, вам также нужно имя файла и информация файловой системы о его происхождении.

Представьте 20 файлов JavaScript, которые вы хотите минимизировать. Вы должны запомнить каждое имя файла отдельно и отслеживать, какие данные относятся к каждому из них, чтобы восстановить связь после того, как результат (миниатюрные файлы с таким же именем) должен быть сохранен.

К счастью, Gulp позаботился об этом за вас, создав и новый источник ввода, и тип данных, который можно использовать для ваших потоков: виртуальные файловые объекты.

После открытия Gulp потока все исходные физические файлы оборачиваются в виртуальный файловый объект и обрабатываются в виртуальной файловой системе — Vinyl (соответствующее программное обеспечение вызывается в Gulp).

Vinyl объекты, файловые объекты вашей виртуальной файловой системы, содержат два типа информации: путь, из которого был создан файл, становящийся именем файла, а также поток, отображающий содержимое файла. Эти виртуальные файлы хранятся в памяти вашего компьютера, известной как самый быстрый способ обработки данных.

Именно там происходят все изменения, которые обычно совершаются на вашем жестком диске. Сохраняя все в памяти и не требуя дорогостоящих операций чтения и записи между процессами, Gulp может вносить изменения чрезвычайно быстро.

Внутри Gulp использует объектные потоки, чтобы выдавать файл за файлом в конвейер обработки. Потоки объектов ведут себя как обычные потоки, но вместо буферов и строк мы проходим через старые добрые объекты JavaScript.

Мы можем создать наш собственный читаемый объектный поток, используя пакет readable-stream:

  1. Для создания читаемого объекта важно установить флаг objectMode в значение true . При этом поток может передавать объекты JavaScript через конвейер. В противном случае предполагается использование буферов или строк.
  2. Каждому потоку нужна функция _read . Эта функция вызывается, когда поток проверяет данные. Это правильное место, чтобы запустить другие механизмы и вставить обновленное содержимое в поток. Поскольку мы вставляем данные извне, нам не нужна эта функция, и мы можем оставить ее пустой. Однако читаемые потоки должны ее реализовывать, иначе мы получим ошибку.
  3. Здесь мы заполняем поток демонстрационными данными. Каждые 100 миллисекунд мы вставляем в наш поток объект со случайным числом.
  4. Так как мы хотим передать результаты объектного потока в process.stdout , а process.stdout принимает только строки, у нас имеется небольшой трансформатор, где мы извлекаем свойство из переданного JavaScript объекта.
  5. Мы создаем конвейер. Наш читаемый объектный поток передает все свои данные в трансформатор getX и, наконец, в записываемый process.stdout .

Вы могли заметить, что для работы с потоками мы используем различные npm-пакеты. Разве это не странно? Потоки очень важны для асинхронного ввода-вывода, не должны ли они быть частью ядра Node.js?

Тем не менее, ядро потоковой передачи постоянно подвергалось изменениям в старые 0.x дни Node.js, поэтому сообщество вмешалось и создало прочный и стабильный API вокруг базовых пакетов. При семантическом версионировании вы можете быть уверены, что экосистема потоков в вашем приложении чувствует себя прекрасно.

Хорошо! Давайте рассмотрим небольшое приложение, считывающее данные CSV и сохраняющее их в JSON. Мы хотим использовать объектные потоки, потому что в некоторых случаях мы можем захотеть изменить данные в зависимости от ситуации. Поскольку потоки очень крутые и позволяют нам это, мы хотим иметь возможность выводить результат в различных форматах.

Во-первых, мы устанавливаем несколько пакетов:

  1. through2 мы уже знаем. Его мы используем для создания всех наших трансформаторов.
  2. Пакет fs очевидно предназначен для чтения и записи файлов. Отличная новость: он позволяет создавать читаемые потоки! Именно то, что нам нужно.
  3. Поскольку вы никогда не знаете, как данные из fs.createReadStream загружаются в вашу память, пакет split2 гарантирует, что вы можете обрабатывать данные построчно. Обратите внимание на «2» в имени этого трансформатора. Она говорит вам, что он является частью более крупной сематически версионируемой экосистемы.

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

В этом примере первая строка всегда является направляющей для наших данных. Поэтому мы хотим обработать ее особым образом: она предоставит ключи для наших объектов JSON.

  1. Мы создаем трансформатор для объектного потока. Обратите внимание на метод .obj . Даже если ваши входные данные - это просто строки, вам нужен трансформатор объектного потока, если вы хотите продолжать выпускать объекты.
  2. В этом блоке мы парсим направляющую строку (разбитую на запятые). Это будет наш шаблон для ключей. Мы удаляем эту строку из потока, поэтому передаем null оба раза.
  3. Для всех остальных строк каждый объект мы создаем с помощью шаблона ключей, который мы распарсили ранее.
  4. Мы передаем этот объект на следующий этап.

Это все, что нужно для создания JavaScript объекта из CSV-файла!

Когда у нас есть все возможности объектов, мы можем намного проще преобразовать данные. Удалять свойства и добавлять новые; осуществлять filter , map и reduce . Все, что вы любите. Пример этого не хочется усложнять: выберите первые 10 записей:

Как и в предыдущем примере: Передача данных для второго аргумента обратного вызова означает, что мы сохраняем элемент в потоке. Передача null означает, что мы отбрасываем данные. Это важно для фильтров!

Вы знаете, что означает JSON? Нотация (запись) объектов JavaScript (JavaScript object notation). Это замечательно, потому что у нас есть объекты JavaScript, и мы можем записать их в строковом представлении!

Итак, что мы хотим сделать с объектами в нашем потоке, — это собрать все проходящие объекты и сохранить их в одно строковое представление. Сразу приходит в голову JSON.stringify .

Одна важная вещь, которую вы должны знать при работе с потоками, состоит в том, что после того, как объект (или данные буфера, если это важно) проходит через ваш трансформатор к следующему этапу, в этом этапе он более не доступен.

Это означает, что вы можете передавать объекты только на один записываемый поток. Существует, однако, способ сбора данных и выполнения с ними каких-либо иных действий. Если в потоке больше нет данных, каждый трансформатор вызывает метод flush (промывать).

Подумайте о раковине, наполняющейся жидкостями.

Вы не можете выбрать каждую ее каплю и проанализировать ее снова. Но вы можете промыть все это до следующего этапа. Это то, что мы делаем в следующем трансформаторе — toJSON :

  1. Мы собираем все проходящие данные в массив и удаляем объекты из нашего потока.
  2. Во второй функции обратного вызова, метод flush , мы преобразуем собранные данные в строку JSON. С помощью this.push мы помещаем этот новый объект в следующий этап нашего потока. В этом примере новый «объект» - просто строка - что-то, что совместимо с обычными записываемыми потоками!

Gulp, например, использует это поведение при работе с плагинами конкатенации. Чтение всех файлов в первой стадии, а затем очистка одного файла для следующего этапа.

Снова приходит в голову функциональное программирование: каждый трансформатор, который мы написали, полностью отделен от остальных. И они идеально подходят для разных сценариев, независимо от входных данных или формата вывода.

Единственные ограничения в формате CSV (первая строка — это заголовок) и в том, что pickFirst10 и toJSON нуждаются в объектах JavaScript в качестве входных данных. Давайте объединим их и выведем первые десять записей в JSON в стандартную консоль:

Это великая сила потоков Node.js. У вас есть асинхронный способ обработки ввода и вывода, и вы можете преобразовывать данные в независимых шагах. С объектными потоками вы можете использовать объекты JavaScript, которые вы знаете и которые любите преобразовывать.

Это основа Gulp как потоковой системы сборки, но также отличный инструмент для повседневной разработки.

Читайте также: