20. Node.js Lessons. Data Streams in Node.JS, fs.ReadStream

59OoKOp

Hey all! Our topic for today is Data Streams In Node.js. We will try to learn all the aspects in details for the reason it turns out that on the one hand, common browser JavaScript development lack streams. And on the other hand, knowing and understanding stream principles is necessary for seamless server development because a stream is a versatile way of work with data sources universally used.

We can define two general stream types. The first one is

stream.Readable

It is a built-in class providing streams for reading. Generally, this type itself is never used, while its descendants are quite popular – in particular, we use fs.ReadStream to read from a file. To read from a visitor’s request for its handling, there is a special object familiar to us under its name req, which is the first argument of a request handler.

Stream.Writable

It is a versatile writing method. The very stream.Writable is rarely used, but its descendants – fs.WriteStream and res are quite common.

There are some other stream types, but the most popular are these two and their variations.

The best way to understand streams is to see how they work in practice. So, right now we’ll start with using fs.ReadStream for reading a file. Let us create a file fs.js:

So, we get the module fs connected and create a stream:

Stream is a JavaScript object receiving information about our resource – in our case, it is a path to the file (__filename) – which can work with this resource. fs.ReadStream implements a standard reading interface described in the stream.Readable class. Let us have a detailed look.

scheme 20 node

When a stream object new stream.Readable is created, it gets connected to the data source, which is file in our case, and tries to start reading from it. Once it has read something, it imitates the event readableThis event means that all the data have been computed and are contained within an inner stream buffer that can be received using the call read()Then we can do something with data and wait for the next readableThis cycle will be the same.

Whenever the data source gets empty (however, there are certain sources that never get empty – for example, a random data generator), the file size is limited, so we will have the end, event in the very end meaning there will be no data anymore. Moreover we can call the method destroy() at any step of working with the stream. This method means we do not need the stream anymore and it can be closed, as well as the respective data sources and everything can be cleaned up.

So, let us refer to the original code. Here we create ReadStream, and it immediately wants to open up a file:

but in our case it doesn’t necessarily mean the same string because any input/output-related operation is performed through libUV. At the same time, libUV has a structure that enables all synchronous input/output handlers to get implemented during the next event loop iteration, or once the current JavaScript has finished its work. It means, we can seamlessly use all handlers knowing that they will be installed prior to the moment the first data fragment gets read. Launch fs.js.

Look at what has appeared in the console. The first one was the event readable. It outputted data. Right now it is an ordinary buffer, but we can transform it to the string by specifying the coding directly upon the stream opening.

Thus, the modification will be automatic. When a file ends, the event end outputs THE END in the console. Here the file ended almost immediately because it was small at the moment. Let us modify our example a little bit by making a file big.html out of the current file contained in the current directory. Download this HTML file from our repository together with the other lesson materials.

Launch it. The file big.html is big, so the event readable has been initiated several times, and every time we received another data fragment as a buffer. So, let us calculate its length:

Get it launched. These numbers are the read file fragment length. When a stream opens a file, it reads only its part, but not the whole file, and inserts it into its internal variable. The maximum size is exactly 64 KB. Until we call stream.Readit won’t read further. Once we’ve received the data, the internal buffer cleans up and can be ready for reading another abstract, etc. The last abstract length is 60,959 B. This example has vividly demonstrated the key advantages of stream usage. They help save memory. Whatever is the size our big file, we still handle only its small part at a moment. The second less obvious advantage is versatility of its interface. Here we use the stream ReadStream from the file. But we can replace it any time by any stream from our resource:

It won’t need any change of the left code because streams are, first of all, our interface. So, it means, if theoretically our stream performs all needed events and methods – in particular, it inherits from stream.Readable – everything should be ok. Of course, it will happen only if we do not use any special abilities that only file streams have got. To be more specific, the stream fs.ReadStream has extra events

scheme 20 node2

Here we can see a draft exactly for fs.ReadStreamnew events are colored in red. First, it is a file opening, while the last event is its closure. Focus your attention on the fact that if a file is read till its end, the end event occurs followed by close. And if a file is not entirely read – for instance, because of an error or upon calling the destroy method – there will be no end because the file hasn’t been ended. But the event close is always ensured upon a file closure.

Finally, our last, but not least detail here is error handling. So, let us see what will happen, if there is no file.

So, I get it launched. Oops! It crashed! Pay your attention to the fact the streams inherit from EventEmitter. If an error occurs, the whole Node.js process fails. It happens if an error of this kind does not have any handler. That’s why if we do not want our Node.js to fail because of an exception, we should install a handler:

So, we use streams to work with data sources in Node.js. Here we’ve analyzed a basic scheme, according to which they work, and a particular example – fs.ReadStream – that can read from a file.

This lesson’s coding can be found in our repository.

Cities_Black_and_white_photos_of_Paris_098171_

The article materials were borrowed from the following screencast.

We are looking forward to meeting you on our website soshace.com

About the author

Stay Informed

It's important to keep up
with industry - subscribe!

Stay Informed

Looks good!
Please enter the correct name.
Please enter the correct email.
Looks good!

20. Уроки Node.js. Потоки данных в Node.JS, fs.ReadStream

59OoKOp
Всем привет! Тема этого занятия: Потоки данных в Node.js. Мы постараемся разобраться в этой теме более подробно, поскольку, с одной стороны, так получается, что потоки в обычной браузерной  JavaScript разработке отсутствуют, а с другой стороны, уверенное владение потоками необходимо для грамотной серверной разработки, поскольку поток является универсальным способом работы с источниками данных, которые используются повсеместно.

Можно выделить два основных типа потоков: первый поток –

stream.Readable

Это встроенный класс, который реализует потоки для чтения. Как правило, сам он не используется, а используются его наследники, в частности, для чтения с файла есть fs.ReadStream, для чтения  с запроса посетителя при его обработке есть специальный объект, который мы раньше видели под именем req – первый аргумент обработчика запроса.

Stream.Writable

Это универсальный способ записи. Сам stream.Writable обычно не используется, но используются его наследники: fs.WriteStream и res.

Есть и некоторые другие типы потоков, но наиболее востребованы эти два и производные от них.

Самый лучший способ разобраться с потоками – это посмотреть, как они работают на практике. Поэтому сейчас мы начнем с того, что используем  fs.ReadStream для чтения файла. Создадим файл fs.js:

Итак,  подключаем модуль fs и создаем поток:

Поток – это JavaScript-объект, который получает информацию о ресурсе, в данном случае путь к файлу (__filename), и который умеет с этим ресурсом работать.  fs.ReadStream реализует стандартный интерфейс чтения, который описан в классе stream.Readable. Давайте рассмотрим более подробно.

scheme 20 node

Когда создается объект потока (new stream.Readable) , то он подключается к источнику данных, в нашем случае это файл, и пытается начать из него читать. Когда он что-то прочитал, то имитирует событие “readable”. Это событие означает, что данные просчитаны и находятся во внутреннем буфере потока, который мы можем получить, используя вызов read(). Затем мы что-то можем делать с данными (“data”) и подождать следующего “readable”. И так дальше по кругу.

Когда источник данных иссяк, бывают, конечно, источники, которые не иссякают, например, датчики случайных чисел, но размер файла то ограничен, поэтому в конце будет событие “end”, которое означает, что данных больше не будет. Также на любом этапе работы с потоком мы можем вызвать метод destroy() потока. Этот метод означает, что мы больше не нуждаемся в потоке и можно его закрыть, а также закрыть соответствующие источники данных, полностью все очистить.

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

но в данном случае, вовсе не означает, на этой же строке, потому что все операции, связаны с вводом-выводом, реализуются через libUV. А libUV устроено так, что все синхронные обработчики ввода-вывода сработают на следующей итерации событийного цикла, то есть, заведомого после того, как весь текущий JavaScript закончит работу. Это означает, что мы можем без проблем навесить все обработчики, и мы твердо знаем, что они будут установлены до того, как будет считан первый фрагмент данных. Запускаем fs.js.

Смотрим, что вывелось в console.  Первым сработало событие readable. Оно вывело данные. Сейчас это обычный буфер, но можно преобразовать его к строке, указав кодировку непосредственно при открытии потока.

Таким образом  преобразование будет автоматическим. Когда файл закончился, то событие end вывело THE END в консоль. Здесь файл закончился почти сразу, поскольку он был еще маленьким. Сейчас  немного модифицируем пример, сделав вместо текущего файла файл big.html, который находится в текущей директории. Скачайте этот HTML файл из нашего репозитория вместе с остальными материалами урока.

Запускаем. Файл big.html большой, поэтому событие readable срабатывало многократно, и каждый раз мы получали очередной фрагмент данных  в виде буфера. Давайте, выведем его длину:

Запускаем. Эти числа – это длина прочитанного фрагмента файла. Потому что когда поток открывает файл, он читает из него только кусок, а не весь файл, и помещает его в свою внутреннюю переменную. Максимальный размер – это как раз 64 кВ. Пока мы не вызовем stream.Read, он дальше читать не будет. После того, как мы получим эти данные, внутренний буфер очищается и он может прочитать следующий фрагмент, и т.д. Длина последнего фрагмента – 60959 В. На этом примере мы отлично видим важные преимущества использования потоков. Они экономят память. Каков бы большой файл не был, все равно единовременно мы обрабатываем небольшой фрагмент. Второе, менее очевидное, преимущество – это универсальность интерфейса. Здесь мы используем поток ReadStream из файла. Но мы можем в любой момент заменить его на произвольный поток из нашего ресурса:

Это не потребует изменения оставшейся части кода, потому что потоки – это, в первую очередь, интерфейс. То есть, в теории, если наш поток реализует необходимые события и методы, в частности, наследует от stream.Readable, то все должно работать хорошо. Это, конечно, только в том случае, если мы не использовали специальных возможностей, которые есть только у файловых потоков. В частности, у потока fs.ReadStream есть дополнительные события.

scheme 20 node2

Здесь изображена схема именно для  fs.ReadStream, новые события изображены красным цветом. В начале это открытие файла, а в конце – закрытие. Обратим внимание, если файл полностью дочитан, то возникает событие end, а затем close. А если файл недочитан, например, из-за ошибки или при вызове метода destroy, то end не будет, поскольку файл не закончился, но всегда гарантируется при закрытии файла событие close.

И, наконец, последнее по коду, но отнюдь не последняя по важности деталь – обработка ошибок. Например, посмотрим, что будет, если файла нет.

Запускаю. Упс! Все упало! Обратите внимание, потоки наследуют от EventEmitter. Ели происходит ошибка, то весь процесс Node.js падает. Это в том случае, если на эту ошибку нет обработчиков. Поэтому, если мы хотим, чтобы в случае чего Node.js вообще не упал из-за исключения, то нужно обязательно поставить обработчик:

Итак, для работы с источниками данных в Node.js используются потоки. Здесь мы рассмотрели общую схему, по которой они работают, и ее конкретную реализацию, а именно fs.ReadStream, которая умеет читать из файла.

Код урока Вы можете найти в нашем репозитории.

Cities_Black_and_white_photos_of_Paris_098171_

Материалы для статьи взяты из следующего скринкаста.

We are looking forward to meeting you on our website soshace.com

About the author

Stay Informed

It's important to keep up
with industry - subscribe!

Stay Informed

Looks good!
Please enter the correct name.
Please enter the correct email.
Looks good!

Related articles

26.03.2024

An Introduction to Clustering in Node.js

Picture your Node.js app starts to slow down as it gets bombarded with user requests. It's like a traffic jam for your app, and no developer likes ...

15.03.2024

JAMstack Architecture with Next.js

The Jamstack architecture, a term coined by Mathias Biilmann, the co-founder of Netlify, encompasses a set of structural practices that rely on ...

Rendering Patterns: Static and Dynamic Rendering in Nextjs

Next.js is popular for its seamless support of static site generation (SSG) and server-side rendering (SSR), which offers developers the flexibility ...

No comments yet

Sign in

Forgot password?

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy

Password recovery

You can also try to

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy