Qué son los Streams en NodeJS, cómo podemos usar streams para comunicar un flujo de información de un lugar a otro.
Otro de los conceptos básicos que se manejan muy habitualmente en NodeJS son los streams. En general son flujos de información, o chorros de información que usamos en la transmisión de datos binarios.
El flujo de información que forma un stream se transmite en pedazos, conocidos habitualmente con su término en inglés "chunk". Los chunk no son más que objetos de la clase Buffer, que conocimos en el artículo anterior Buffer en NodeJS.
Es poco habitual crear strings desce cero en un programa, generalmente los usaremos cuando consultemos información que viene de diversas fuentes de datos. En este artículo veremos algunos conceptos generales sobre streams y trabajaremos con streams que nos vienen dados mediante la lectura de archivos de texto que tenemos en el sistema de archivos del ordenador. Sin embargo, los streams los vamos a recibir desde muchas fuentes de información como la manipulación de imágenes, las request del protocolo HTTP o los sockets.
Existen tres tipos de streams, según el uso que queramos realizar mediante estos flujos de datos. Los tenemos de lectura, escritura y duplex (que permiten ambas operaciones a la vez).
Crear un stream a partir de un archivo de texto
Aunque no hemos visto todavía cómo realizar la lectura de archivos de texto en NodeJS, vamos a apoyarnos en el sistema de archivos del sistema para acceder a un stream de información, con el que realizar algunos ejemplos básicos.
Antes de nada, si vamos a usar el sistema de archivos del ordenador, tenemos que hacer un require del módulo 'fs' (file system).
var fs = require('fs');
El método createReadStream() del objeto fs nos devuelve un stream a cambio de la ruta del archivo que pretendemos leer.
var streamLectura = fs.createReadStream('./archivo-texto.txt');
Al usar createReadStream() recibiremos un stream de lectura. Con él podremos hacer todas las cosas que se encuentran disponibles en el API de NodeJS para los streams. Ahora veremos un par de ejemplos sencillos, pero recuerda que tienes la documentación de streams para obtener más información.
Eventos en streams
Existen varios eventos que podemos usar con los streams, para realizar acciones cuando ocurran ciertos sucesos. Los eventos disponibles dependen del tipo de stream que tenemos con nosotros. Por ejemplo, para streams de lectura tenemos los eventos "close", "data", "end", "error", "readable".
El evento "data" ocurre cada vez que se reciben datos desde un stream, lo que ocurre al invocar diversos métodos del objeto stream. Además, al crearse un manejador de evento para el evento "data", comienza también la lectura del stream. Cuando el dato se haya leído y se encuentre disponible se ejecutará el propio manejador de evento asociado, recibiendo como parámetro un buffer de datos.
streamLectura.on('data', (chunk) => {
//chunk es un buffer de datos
console.log(chunk instanceof Buffer); //escribe "true" por pantalla
});
Con ese flujo de datos podemos hacer cosas de las que hemos aprendido en el artículo anterior de los buffer. Por ejemplo podríamos ver su contenido de esta manera:
streamLectura.on('data', (chunk) => {
console.log('He recibido ' + chunk.length + ' bytes de datos.');
console.log(chunk.toString());
});
Stream de escritura process.stdout
Para hacer alguna cosa con streams de tipo de escritura vamos a basarnos en una propiedad del objeto global process de Node, llamada stdout. No es más que un stream de escritura con el que podemos generar salida en nuestro programa. La salida de stdout es la salida estándar de NodeJS, la propia consola.
Mediante el método write() se escribe en un stream de escritura. Para ello tenemos que enviarle un buffer y otra serie de parámetros opcionales como el tipo de codificación y una función callback a ejecutar cuando termine la operación de escritura.
process.stdout.write(objetoBuffer);
Como imaginarás, nuestro manejador de evento anterior podría apoyarse en este método write() para conseguir la escritura, en lugar de usar el console.log().
streamLectura.on('data', (chunk) => {
process.stdout.write(chunk)
});
Usar la entrada estándar process.stdin
Igual que existe un process.stdout para la salida estándar, process.stdin es un stream de lectura para la entrada estándar. Por medio de éste podremos obtener entrada de datos por consola. El proceso no es tan simple como para realizarse en una única acción, como un prompt() de Javascript del lado del cliente, pero se puede entender bien.
Comenzamos con la configuración de la entrada de datos por consola.
process.stdin.setEncoding('utf8');
Luego podemos mostrar un mensaje en la consola para que se sepa qué dato se está solicitando al usuario.
process.stdout.write('¿Qué sugerencias puedes dar a DesarrolloWeb.com? ');
Posteriormente podemos asociar un manejador de eventos a stdin, para el evento "data". Este evento en sí lo acabamos de conocer hace un poco. Él produce que la lectura comience y una vez que se tenga algún dato, ejecutará la función callback. En la función callback recibiremos el chunk (buffer) de datos escritos en la consola hasta la pulsación de la tecla enter.
process.stdin.once('data', function(res) {
process.stdout.write('Has respondido: ');
process.stdout.write(res);
process.stdin.pause();
});
Dentro de la función callback producimos un poco de salida, mostrando entre otras cosas aquello que se escribió en la consola.
Por último ejecutamos el método pause() que producirá que el stream pare de emitir datos, por lo que se dejará de leer en stdin y por tanto el programa acabará.
Generar tuberías entre streams
Podemos conectar un stream de lectura a un stream de escritura, produciendo una tubería que enviará los datos del origen para el destino. Para ello usamos el método pipe().
Para crear esa tubería tenemos que invocar el método pipe sobre un stream de lectura. Ahora, además del stream de lectura del archivo de texto de antes, vamos a crear un stream de escritura sobre otro archivo del sistema.
var streamLectura = fs.createReadStream('./archivo-texto.txt');
var streamEscritura = fs.createWriteStream('./otro-archivo.txt');
Ahora vamos a usar el método pipe() para realizar ese flujo de datos de un stream a otro.
streamLectura.pipe(streamEscritura);
Si ejecutamos ese método produciremos la copia del contenido del archivo "archivo-texto.txt" hacia el fichero "otro-archivo.txt".
Podemos saber cuándo terminó esa copia del fichero si añadimos un evento "end" a nuestro stream de lectura.
streamLectura.on('end', function() {
console.log('La lectura del fichero se ha completado');
});
Conclusión
Hemos conocido los streams de NodeJS, tanto de lectura como escritura, y hemos experimentado con algunas funcionalidades de su API. También hemos abordado los streams de lectura y escritura por la entrada/salida estándar en Node, stdin y stdout. Pero además hemos podido darle un poco más de sentido a los buffer de Node, abordados en el artículo anterior, realizando diversas operativas con ellos.
Claro que hay mucho más que aprender pero estamos seguros que este conocimiento general serás capaz de entender muchos de los procedimientos habituales en el trabajo con NodeJS.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...