Skip to content

Latest commit

 

History

History
276 lines (188 loc) · 8.58 KB

45.streams_no_node.md

File metadata and controls

276 lines (188 loc) · 8.58 KB

Streams no Node.js

Índice

O que são streams

Os streams são um dos conceitos fundamentais que alimentam os aplicativos Node.js.

Eles são uma maneira de lidar com a leitura/gravação de arquivos, comunicações de rede ou qualquer tipo de troca de informações de ponta a ponta de maneira eficiente.

Streams não são um conceito exclusivo do Node.js. Eles foram introduzidos no sistema operacional Unix décadas atrás, e os programas podem interagir uns com os outros passando fluxos através do operador pipe (|).

Por exemplo, da maneira tradicional, quando você diz ao programa para ler um arquivo, o arquivo é lido na memória, do início ao fim, e então você o processa.

Usando streams você lê peça por peça, processando seu conteúdo sem manter tudo na memória.

O módulo stream do Node.js fornece a base sobre a qual todas as APIs de streaming são criadas. Todos os streams são instâncias de EventEmitter.

Por quê streams

Streams basicamente fornecem duas vantagens principais sobre o uso de outros métodos de manipulação de dados:

  • Eficiência de memória: você não precisa carregar grandes quantidades de dados na memória antes de poder processá-los.
  • Eficiência de tempo: leva muito menos tempo para iniciar o processamento de dados, pois você pode iniciar o processamento assim que os tiver, em vez de esperar até que toda a carga de dados esteja disponível.

Um exemplo de um stream

Um exemplo típico é a leitura de arquivos de um disco.

Usando o módulo fs do Node.js, você pode ler um arquivo e servi-lo por HTTP quando uma nova conexão for estabelecida com seu servidor HTTP:

const http = require('http');
const fs = require('fs');

const server = http.createServer(function (req, res) {
  fs.readFile(`${__dirname}/data.txt`, (err, data) => {
    res.end(data);
  });
});
server.listen(3000);

readFile() lê o conteúdo completo do arquivo e invoca a função de callback quando termina.

res.end(data) na callback retornará o conteúdo do arquivo para o cliente HTTP.

Se o arquivo for grande, a operação levará um pouco de tempo. Aqui está a mesma coisa escrita usando streams:

const http = require('http');
const fs = require('fs');

const server = http.createServer((req, res) => {
  const stream = fs.createReadStream(`${__dirname}/data.txt`);
  stream.pipe(res);
});
server.listen(3000);

Em vez de esperar até que o arquivo seja totalmente lido, começamos a transmiti-lo para o cliente HTTP assim que temos um bloco de dados pronto para ser enviado.

pipe()

O exemplo acima usa a linha stream.pipe(res): o método pipe() é chamado no stream do arquivo.

O que faz este código? Ele pega a fonte e a canaliza para um destino.

Você o chama no stream de origem, portanto, nesse caso, o stream de arquivo é canalizado para a resposta HTTP.

O valor de retorno do método pipe() é o stream de destino, que é uma coisa muito conveniente que nos permite encadear várias chamadas pipe(), assim:

src.pipe(des1).pipe(dest2);

Esta construção é o mesmo que fazer:

src.pipe(dest1);
dest1.pipe(dest2);

APIs Node.js baseadas em streams

Devido às suas vantagens, muitos módulos principais do Node.js fornecem recursos nativos de manipulação de stream, principalmente:

  • process.stdin retorna um stream conectada ao stdin.
  • process.stdout retorna um stream conectado ao stdout.
  • process.stderr retorna um stream conectado ao stderr.
  • fs.createReadStream() cria um arquivo readable stream.
  • fs.createWriteStream() cria um arquivo writable stream.
  • net.connect() inicia uma conexão baseada em stream.
  • http.request() retorna uma instância da classe http.ClientRequest, que é um writable stream.
  • zlib.createGzip() compacta um dado usando gzip (um algoritmo de compressão) para um stream.
  • zlib.createGunzip() descompacta um stream gzip.
  • zlib.createDeflate() compacta dados usando deflate (um algoritmo de compressão) para um stream.
  • zlib.createInflate() descompacta um stream deflate.

Diferentes tipos de stream

Existem quatro classes de stream:

  • Readable: um stream que pode ser usado para ler dados dele. Em outras palavras, é apenas readonly (RO).
  • Writable: um stream que pode ser usado para gravar dados nele. É apenas writeonly (WO).
  • Duplex: um stream que pode ler e gravar dados, basicamente é uma combinação de um Readable stream e Writable stream.
  • Transform: um stream Duplex que lê os dados, transforma os dados e, em seguida, grava os dados transformados no formato desejado.

Como criar um readable stream

Obtemos o Readable stream do módulo stream, inicializamos e implementamos com o método readable._read().

Primeiro crie um objeto de stream:

const Stream = require('stream');

const readableStream = new Stream.Readable();

então implemente o _read:

readableStream._read = () => {};

Você também pode implementar o _read usando a opção read:

const readableStream = new Stream.Readable({
  read() {},
});

Agora que a stream foi inicializada, nós podemos enviar dados para ela:

readableStream.push('hi!');
readableStream.push('ho!');

Como criar um writable stream

Para criar um writable stream, estendemos o objeto Writable básico e implementamos seu método _write().

Primeiro crie um objeto de stream:

const Stream = require('stream');

const writableStream = new Stream.Writable();

então implemente o _write:

writableStream._write = (chunk, encoding, next) => {
  console.log(chunk.toString());
  next();
};

Agora você pode canalizar um readable stream em:

process.stdin.pipe(writableStream);

Como lemos dados de um readable stream

Como lemos dados de um readable stream? Usando um writable stream:

const Stream = require('stream');

const readableStream = new Stream.Readable({
  read() {},
});
const writableStream = new Stream.Writable();

writableStream._write = (chunk, encoding, next) => {
  console.log(chunk.toString());
  next();
};

readableStream.pipe(writableStream);

readableStream.push('hi!');
readableStream.push('ho!');

Você também pode consumir um readable stream diretamente, usando o readable event:

readableStream.on('readable', () => {
  console.log(readableStream.read());
});

Como enviar dados para um writable stream

Usando o método write().

writableStream.write('hey!\n');

Sinalizando para um writable stream que você terminou de escrever

Use o método end():

const Stream = require('stream');

const readableStream = new Stream.Readable({
  read() {},
});
const writableStream = new Stream.Writable();

writableStream._write = (chunk, encoding, next) => {
  console.log(chunk.toString());
  next();
};

readableStream.pipe(writableStream);

readableStream.push('hi!');
readableStream.push('ho!');

readableStream.on('close', () => writableStream.end());
writableStream.on('close', () => console.log('ended'));

readableStream.destroy();

No exemplo acima, end() é chamado dentro de um listener para o evento close no readable stream para garantir que ele não seja chamado antes que todos os eventos de gravação tenham passado pelo pipe, pois isso faria com que um evento error fosse emitido. Chamar destroy() no readable stream faz com que o evento close ja emitido. O listener do evento close no writable stream demonstra a conclusão do processo conforme ele é emitido após a chamada para end().

Como criar um transform stream

Obtemos o Transform stream do módulo stream, inicializamos e implementamos com o método transform._transform().

Primeiro, crie um objeto transform stream:

const { Transform } = require('stream');

const transformStream = new Transform();

então implemetamos o _transform:

transformStream._transform = (chunk, encoding, callback) => {
  transformStream.push(chunk.toString().toUpperCase());
  callback();
};

Pipe para um readable stream:

process.stdin.pipe(transformStream).pipe(process.stdout);