Escribir y leer archivos grandes con Node.js - Transforms (Serie Node.js archivos grandes - Parte 2)
Ariel Alvarado | Junio 22, 2020
Ya resolvimos el problema de lectura y escritura de archivos grandes en otro post, pero la solución no es muy intuitiva (muchos no están acostumbrados a utilizar eventos) por lo que ahora modificaremos la solución para que sea más facíl de entender.
Recordemos que lo que queremos lograr es reemplazar todas la ocurrencias del número 0 por la palabra Cero y escribir el resultado en un archivo.
Pipes
Pipes son canales donde entra un stream (readable stream) y sale otro (writable stream) y hacen que sea más sencillo entender el flujo de un stream. Además, evita el problema de que el stream de lectura sea mucho más rápido que el stream de escritura, lo cuál hace que demasiada información se acumule esperando para ser procesada en el stream de salida.
Veamos un ejemplo:
const path = require("path");
const fs = require("fs");
const { Transform } = require("stream");
const rutaDelArchivoParaLeer = path.join(__dirname, "big-file.txt");
const streamDeLectura = fs.createReadStream(rutaDelArchivoParaLeer, {
autoClose: true,
});
// creamos un Transform (un transform espera un stream y devuelve otro stream transformado)
const reemplazarCaracteresTransform = new Transform({
// creamos el método que transformará la información enviada
transform(chunk, encoding, callback) {
const textoParcial = chunk.toString();
const textoParcialReemplazado = textoParcial.replace(/0/g, "Cero");
// agregamos el contenido transformado (pieza por pieza) a la cola para que los usuarios del stream puedan usarlo
this.push(textoParcialReemplazado);
callback();
},
});
streamDeLectura
.pipe(reemplazarCaracteresTransform)
// process.stdout es un stream de escritura
.pipe(process.stdout)
.on("finish", () => console.log("Proceso terminado"));
Como se puede observar, el flujo de la información es mucho más simple, utilizamos el stream de lectura y vamos "encadenando" el el flujo de datos con pipes. Además, esperamos el evento finish que es lanzado cuando todo el flujo de datos ha terminado.
Ahora, completaremos el ejemplo del post anterior:
const path = require("path");
const fs = require("fs");
const { Transform } = require("stream");
const rutaDelArchivoParaLeer = path.join(__dirname, "big-file.txt");
const streamDeLectura = fs.createReadStream(rutaDelArchivoParaLeer, {
autoClose: true,
});
const rutaDelArchivoParaEscribir = path.join(__dirname, "big-file-write.txt");
const streamDeEscritura = fs.createWriteStream(rutaDelArchivoParaEscribir);
// transform para reemplazar caracteres
const reemplazarCaracteresTransform = new Transform({
transform(chunk, encoding, callback) {
const textoParcial = chunk.toString();
const textoParcialReemplazado = textoParcial.replace(/0/g, "Cero");
this.push(textoParcialReemplazado);
callback();
},
});
streamDeLectura
.pipe(reemplazarCaracteresTransform)
.pipe(streamDeEscritura)
.on("finish", () => console.log("Proceso terminado"));
Muy simple y fácil de entender (similar al manejo de promesas), además de consumir pocos recursos y que puedes trabajar con archivos de gran tamaño.
Y eso es todo por ahora, keep coding!!!