Статьи

Основы потоков Node.js

Node.js по своей природе асинхронный и управляемый событиями. В результате он очень хорошо справляется с задачами, связанными с вводом / выводом. Если вы работаете с приложением, которое выполняет операции ввода-вывода, вы можете воспользоваться потоками, доступными в Node.js. Итак, давайте подробно рассмотрим потоки и поймем, как они могут упростить ввод-вывод.

Что такое потоки

Потоки — это каналы Unix, которые позволяют легко считывать данные из источника и направлять их к месту назначения. Проще говоря, поток — это не что иное, как EventEmitter и реализует некоторые специальные методы. В зависимости от реализованных методов поток становится доступным для чтения, записи или дуплекса (как для чтения, так и для записи). Читаемые потоки позволяют вам читать данные из источника, в то время как записываемые потоки позволяют записывать данные в место назначения.

Если вы уже работали с Node.js, возможно, вы сталкивались с потоками. Например, в HTTP-сервере на базе Node.js request является читаемым потоком, а response — записываемым потоком. Возможно, вы использовали модуль fs который позволяет вам работать как с читаемыми, так и с записываемыми потоками файлов.

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

Читаемый поток

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

Чтение из потоков

Лучший способ прочитать данные из потока — это прослушать событие data и прикрепить обратный вызов. Когда кусок данных доступен, читаемый поток генерирует событие data и ваш обратный вызов выполняется. Взгляните на следующий фрагмент:

 var fs = require('fs'); var readableStream = fs.createReadStream('file.txt'); var data = ''; readableStream.on('data', function(chunk) { data+=chunk; }); readableStream.on('end', function() { console.log(data); }); 

Вызов функции fs.createReadStream() дает вам читаемый поток. Первоначально поток находится в статическом состоянии. Как только вы слушаете событие data и присоединяете обратный вызов, оно начинает течь. После этого фрагменты данных читаются и передаются вашему обратному вызову. Разработчик потока решает, как часто происходит событие data . Например, HTTP-запрос может выдать событие data после считывания нескольких килобайт данных. Когда вы читаете данные из файла, вы можете решить, что отправляете событие data после прочтения строки.

Когда больше нет данных для чтения (достигнут конец), поток генерирует событие end . В приведенном выше фрагменте мы слушаем это событие, чтобы получить уведомление о достижении конца.

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

 var fs = require('fs'); var readableStream = fs.createReadStream('file.txt'); var data = ''; var chunk; readableStream.on('readable', function() { while ((chunk=readableStream.read()) != null) { data += chunk; } }); readableStream.on('end', function() { console.log(data) }); 

Функция read() считывает некоторые данные из внутреннего буфера и возвращает их. Когда нечего читать, возвращается null . Итак, в цикле while мы проверяем null и завершаем цикл. Обратите внимание, что readable событие возникает, когда часть потока может быть прочитана из потока.

Настройка кодировки

По умолчанию данные, которые вы читаете из потока, являются объектом Buffer . Если вы читаете строки, это может не подойти для вас. Таким образом, вы можете установить кодировку в потоке, вызвав Readable.setEncoding() , как показано ниже.

 var fs = require('fs'); var readableStream = fs.createReadStream('file.txt'); var data = ''; readableStream.setEncoding('utf8'); readableStream.on('data', function(chunk) { data+=chunk; }); readableStream.on('end', function() { console.log(data); }); 

В приведенном выше фрагменте мы установили кодировку utf8 . В результате данные интерпретируются как utf8 и передаются вашему обратному вызову как строка.

кант

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

 var fs = require('fs'); var readableStream = fs.createReadStream('file1.txt'); var writableStream = fs.createWriteStream('file2.txt'); readableStream.pipe(writableStream); 

Приведенный выше фрагмент использует функцию pipe() для записи содержимого file1 в file2 . Поскольку pipe() управляет потоком данных за вас, вам не нужно беспокоиться о медленном или быстром потоке данных. Это делает pipe() удобным инструментом для чтения и записи данных. Также следует отметить, что pipe() возвращает целевой поток. Таким образом, вы можете легко использовать это для объединения нескольких потоков. Посмотрим как!

Цепной

Предположим, у вас есть архив и вы хотите распаковать его. Есть несколько способов достичь этого. Но самый простой и чистый способ — использовать трубопроводы и цепочки. Посмотрите на следующий фрагмент:

 var fs = require('fs'); var zlib = require('zlib'); fs.createReadStream('input.txt.gz') .pipe(zlib.createGunzip()) .pipe(fs.createWriteStream('output.txt')); 

Сначала мы создаем простой читаемый поток из файла input.txt.gz . Затем мы zlib.createGunzip() этот поток в другой поток zlib.createGunzip() чтобы zlib.createGunzip() содержимое. Наконец, поскольку потоки могут быть объединены в цепочку, мы добавляем записываемый поток, чтобы записать разархивированный контент в файл.

Дополнительные методы

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

  1. Readable.pause() — этот метод приостанавливает поток. Если поток уже выполняется, он больше не будет генерировать события data . Данные будут храниться в буфере. Если вы вызываете это в статическом (не текущем) потоке, поток начинает течь, но события data не будут отправляться.
  2. Readable.resume() — Возобновляет приостановленный поток.
  3. readable.unpipe() — удаляет потоки назначения из каналов назначения. Если аргумент передается, он останавливает поток для чтения в трубопроводе в конкретный целевой поток. В противном случае все потоки назначения удаляются.

Записываемые потоки

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

Запись в потоки

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

 var fs = require('fs'); var readableStream = fs.createReadStream('file1.txt'); var writableStream = fs.createWriteStream('file2.txt'); readableStream.setEncoding('utf8'); readableStream.on('data', function(chunk) { writableStream.write(chunk); }); 

Приведенный выше код является простым. Он просто читает порции данных из входного потока и записывает в место назначения, используя write() . Эта функция возвращает логическое значение, указывающее, была ли операция успешной. Если это true , то запись была успешной, и вы можете продолжать записывать больше данных. Если false возвращается, это означает, что что-то пошло не так, и вы ничего не можете написать в данный момент. Записываемый поток сообщит вам, когда вы сможете начать записывать больше данных, генерируя событие drain .

Конец данных

Если у вас нет больше данных для записи, вы можете просто вызвать end() чтобы сообщить потоку, что вы закончили запись. Предполагая, что res является объектом ответа HTTP, вы часто делаете следующее, чтобы отправить ответ в браузер:

 res.write('Some Data!!'); res.end('Ended.'); 

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

 res.write('Some Data!!'); res.end(); res.write('Trying to write again'); //Error! 

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

  1. error — испускается, чтобы указать, что во время записи / передачи произошла ошибка.
  2. pipe — когда читаемый поток передается в поток для записи, это событие отправляется потоком для записи.
  3. unpipeunpipe когда вы вызываете unpipe в читаемом потоке и не позволяете ему отправлять в поток назначения.

Вывод

Это было все об основах потоков. Потоки, каналы и цепочки являются основными и наиболее мощными функциями Node.js. Если использовать ответственно, потоки действительно могут помочь вам написать аккуратный и производительный код для выполнения ввода-вывода.

Вам понравилась статья? Дайте нам знать, что вы думаете через комментарии.