La ventaja de los streams es que al leer por partes nuestra información usa eficientemente la memoria y toma menos tiempo en procesar la información. Sino usáramos streams tendríamos que esperar a tener toda la información en memoria para luego procesarla.

Que es un stream?

Repositorio

Los streams son el concepto más importante junto con los eventos (un stream es una instancia de un event emitter, los streams (flujo) son información en forma de string o buffer que transitan de un programa o archivo a otro en pequeñas partes usualmente en forma binaria hasta completar toda la información. Este concepto nace en Linux donde la información se origina en alguna parte (teclado, archivo, disco) y fluye hacia otro lugar donde se puede transformar (pantalla, disco, archivo) y todo esto se logra gracias a un estándar llamado stdio (standard input/output).

La ventaja de los streams es que al leer por partes nuestra información usa eficientemente la memoria y toma menos tiempo en procesar la información. Sino usáramos streams tendríamos que esperar a tener toda la información en memoria para luego procesarla.

tipos de streams

En nodeJs existen cuatro tipo de streams:

  • Writable: solo podemos escribir en ellos
  • Readable: solo podemos leer de ellos
  • Duplex: podemos leer y escribir
  • Transform: es un duplex donde a medida que pasa la información la podemos transformar

Estos son algunos ejemplos muy comunes en NodeJs de streams:

  • Un console.log() es una instacia que puede escribir en process.stdout y process.stderr los cuales son streams duplex, osea que pueden leer un mensaje que viene de console.log y escribirlo en nuestra terminal.
// aparece el mensaje en la terminal
console.log("hello world");
  • process.stdin es un stream duplex al igual que process.stdout y podemos pasar información desde nuestra terminal o desde un archivo para que se escriban en stdout por medio del método pipe el cual es una tubería que une nuestros dos streams para que transiten porciones de información.
// stdio.js
process.stdin.pipe(process.stdout)
> node stdio.js
// or 
// cat file.txt | node stdio.js
  • En el siguiente ejemplo podemos ver como por medio de los eventos del request, podemos obtener el stream de información en data, el cual es un buffer y finalmente procesarlo en el evento end, donde ya tenemos todo nuestro buffer completo.
const server = http.createServer((req, res) => {

    const { headers, method, url } = req;
    let body = [];
    let status = 200;
    // event Emitter
    req.on('error', (err) => {
        console.error(err);
    }).on('data', (chunk) => {
        //stream - https://nodejs.org/api/net.html#net_event_data
        body.push(chunk);
    }).on('end', () => {
        body = Buffer.concat(body).toString();
        if (url === "/" && method === "POST") {
            status = 200;
        } else {
            const err = new Error("Not found");
            body = err.message;
            status = 404;
        }
        res.statusCode = status;
        res.setHeader("Content-Type", headers["content-type"]);
        res.end(body);
    });
});

Creando un Readable stream

En un readable stream generamos datos o los leemos, para ello debemos primero usar la interfaz que nos provee Nodejs llamada Readable, donde como mínimo debemos definir el método read, el cual puede ser vacío o tener alguna lógica.

// Leemos pedazos de información que se almacena en un buffer
INPUT  --chunk-->  |* |*| Readable | *| * |  ---> pipe ---> OUTPUT
// readable.js
const Stream = require('stream')
// definimos nuestro Readable con todo por defecto
const readableStream = new Stream.Readable({
    read(size) {}
})

// escribimos en nuestro Readable por medio de push
readableStream.push("Hello world!");
// pasamos nuestro buffer a la terminal
readableStream.pipe(process.stdout)

createReadStream es un Readable del filesystem, con el cual podemos leer de un archivo

// readableExample.js
var fs = require('fs');
var readStream = fs.createReadStream('./output');
readStream.pipe(process.stdout);

Creando un Writable stream

Los Writable streams son nuestro destino donde queremos escribir ya sea la terminal o un archivo.

// La información del buffer la escribimos por pedazos
INPUT  ---> pipe --->  |* |*| Writable | *| * | --chunk--> OUTPUT
// writable.js
const Stream = require('stream')
// definimos nuestro Writable con todo por defecto
const writableStream = new Stream.Writable({
    write(chunk, encoding, next) {
        // por defecto escribimos en la terminal
        console.log(chunk.toString())
        next()
    }
})

process.stdin.pipe(writableStream)

createWriteStream es un Writable del filesystem, con el cual podemos escribir en un archivo.

// writableExample.js
var fs = require('fs');
var writeStream = fs.createWriteStream('./output');
process.stdin.pipe(writeStream)

Creando un Duplex stream

un Duplex stream es la forma de combinar un readable y un writable para que trabajen eficientemente, si nos fijamos bien solo vamos a tener un buffer con duplex mientras que readable y writable tiene su propio buffer osea dos.

// leemos y escribimos pedazos de información
INPUT  --chunk-->  |* |*| duplex | *| * |  --chunk-->  OUTPUT
//duplex.js
const { Duplex } = require('stream');
const duplexStream = new Duplex({
    write(chunk, encoding, callback) {
      console.log(chunk.toString());
      callback();
    },
  
    read(size) {
    }
  });
  
  duplexStream.push("Hello world!");
  duplexStream.pipe(process.stdout);

Crypto es un duplex stream de nodejs con el cual podemos encriptar, esto significa que también es un Transform stream.

// duplexExample.js
const crypto = require('crypto');

const algorithm = 'aes-192-cbc';
const password = 'Password used to generate key';
// Use the async `crypto.scrypt()` instead.
const key = crypto.scryptSync(password, 'salt', 24);
// Use `crypto.randomBytes()` to generate a random iv instead of the static iv
// shown here.
const iv = Buffer.alloc(16, 0); // Initialization vector.
const cipher = crypto.createCipheriv(algorithm, key, iv);


process.stdin.pipe(cipher).pipe(process.stdout);
echo "hello world" | node duplexExample.js

Creando un transform stream

Un Transform stream es básicamente un Duplex donde solo debemos implementar el write que ahora se llama transform y aquí podemos procesar, filtrar, transformar nuestros pedazos de información, esto también se puede hacer en un writable o duplex, pero es mejor tener separado los conceptos.

// transform.js
const { Transform } = require('stream');

const upperCaseTransform = new Transform({
  transform(chunk, encoding, callback) {
      // Convierto a mayúscula
    this.push(chunk.toString().toUpperCase());
    callback();
  }
});

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