Leer archivos con Node.js

Una de las cosas más comunes que querrá hacer con casi cualquier lenguaje de programación es abrir y leer un archivo. Con la mayoría de los idiomas, esto es bastante simple, pero...

Una de las cosas más comunes que querrá hacer con casi cualquier lenguaje de programación es abrir y leer un archivo. Con la mayoría de los lenguajes, esto es bastante simple, pero para los veteranos de JavaScript puede parecer un poco extraño. Durante muchos años, JavaScript solo estuvo disponible en el navegador, por lo que los desarrolladores front-end solo pueden estar familiarizados con la [API del lector de archivos] (http://www.htmlgoodies.com/beyond/javascript/read-text-files -using-the-javascript-filereader.html#fbid=PfBQ7GOxcEB) o similar.

Node.js, como probablemente sepa, es muy diferente a su JavaScript típico en el navegador. Tiene su propio conjunto de bibliotecas diseñadas para manejar tareas del sistema operativo y del sistema de archivos, como abrir y leer archivos. En este artículo, le mostraré cómo usar Node.js para leer archivos. Específicamente, usaremos el módulo fs para hacer precisamente eso.

Hay dos formas de abrir y leer un archivo usando el módulo fs:

  • Cargar todos los contenidos a la vez (buffering)
  • Cargar contenidos de forma incremental (streaming)

Cada uno de estos métodos se explicará en las próximas dos secciones.

Almacenamiento en búfer de contenidos con fs.readFile

Esta es la forma más común de leer un archivo con Node.js, especialmente para principiantes, debido a su simplicidad y conveniencia. Aunque, como se dará cuenta en la siguiente sección, no es necesariamente el mejor o el más eficiente.

Aquí hay un ejemplo rápido usando fs.readFile:

1
2
3
4
5
6
var fs = require('fs');

fs.readFile('my-file.txt', 'utf8', function(err, data) {
    if (err) throw err;
    console.log(data);
});

El argumento data de la devolución de llamada contiene el contenido completo del archivo representado como una cadena en formato utf8. Si omite el argumento utf8 por completo, el método solo devolverá el contenido sin procesar en un objeto Buffer. Eliminando el argumento utf8 en el código anterior (y asumiendo que my-file.txt contenía la cadena "¡Hola!"), obtendríamos este resultado:

1
2
$ node read-file.js
<Buffer 48 65 79 20 74 68 65 72 65 21>

Es posible que haya notado que fs.readFile devuelve el contenido en una devolución de llamada, lo que significa que este método se ejecuta de forma asíncrona. Esto debe usarse siempre que sea posible para evitar bloquear el hilo de ejecución principal, pero a veces tiene que hacer las cosas de forma sincrónica, en cuyo caso Node le proporciona un readFileSync método.

Este método funciona exactamente de la misma manera, excepto que el contenido del archivo se devuelve directamente desde la llamada a la función y el hilo de ejecución se bloquea mientras carga el archivo. Por lo general, uso esto en las secciones de inicio de mis programas (como cuando estamos cargando archivos de configuración) o en aplicaciones de línea de comandos donde bloquear el hilo principal no es gran cosa.

Aquí se explica cómo cargar un archivo de forma síncrona con Node:

1
2
3
4
5
6
7
8
var fs = require('fs');

try {
    var data = fs.readFileSync('my-file.txt', 'utf8');
    console.log(data);    
} catch(e) {
    console.log('Error:', e.stack);
}

Tenga en cuenta que con la llamada de bloqueo (sincrónica) tenemos que usar try...catch para manejar cualquier error, a diferencia de la versión sin bloqueo (asincrónica) donde los errores simplemente se nos pasan como argumentos.

Aparte de la forma en que estos métodos devuelven datos y manejan errores, funcionan de manera muy similar.

Transmisión de contenido con fs.createReadStream

La segunda forma de abrir y leer un archivo es abrirlo como Corriente usando [fs.createReadStream](https://nodejs.org/api /fs.html#fs_fs_createreadstream_path_options) método. Todos los flujos de Node son instancias del objeto EventEmitter, lo que le permite suscribirse a eventos importantes.

Un objeto de flujo legible puede ser útil por muchas razones, algunas de las cuales incluyen:

  • Uso de memoria más pequeño. Dado que los datos del archivo de destino se cargan en fragmentos, no se requiere tanta memoria para almacenar los datos en un búfer.
  • Tiempo de respuesta más rápido. Para aplicaciones sensibles al tiempo, el tiempo entre la solicitud y la respuesta es fundamental. Los flujos reducen el tiempo de respuesta (especialmente para archivos grandes) ya que no necesitan esperar para cargar el archivo completo antes de devolver los datos.
  • Datos de tuberías. La abstracción de flujo le permite usar una interfaz común entre productores y consumidores de datos para pasar esos datos a través de conductos. Esto es muy similar al concepto tubería Unix.

Aunque realmente no es muy difícil usar flujos, pueden ser un poco intimidantes y no son tan intuitivos como el método fs.readFile. Aquí está el 'hola mundo' de la transmisión de archivos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var fs = require('fs');

var data = '';

var readStream = fs.createReadStream('my-file.txt', 'utf8');

readStream.on('data', function(chunk) {
    data += chunk;
}).on('end', function() {
    console.log(data);
});

Este código hace exactamente lo que hace el código de la primera sección, excepto que tenemos que "recopilar" fragmentos de datos antes de imprimirlos en la consola. Si su archivo es bastante pequeño, probablemente solo reciba una sola porción, pero para archivos más grandes, como audio y video, tendrá que recopilar varias porciones. Este es el caso en el que comenzará a notar el valor real de la transmisión de archivos.

Tenga en cuenta que el ejemplo que mostré arriba en su mayoría anula el propósito de usar una transmisión, ya que de todos modos terminamos recopilando los datos en un búfer (variable), pero al menos le da una idea de cómo funcionan. Un mejor ejemplo que muestra las fortalezas de los flujos de archivos se puede ver aquí, en una ruta Express que maneja una solicitud de archivo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
var fs = require('fs');
var path = require('path');
var http = require('http');

var staticBasePath = './static';

var staticServe = function(req, res) {
    var fileLoc = path.resolve(staticBasePath);
    fileLoc = path.join(fileLoc, req.url);
    
        var stream = fs.createReadStream(fileLoc);

        stream.on('error', function(error) {
            res.writeHead(404, 'Not Found');
            res.end();
        });

        stream.pipe(res);
};

var httpServer = http.createServer(staticServe);
httpServer.listen(8080);

Todo lo que hacemos aquí es abrir el archivo con fs.createReadStream y canalizarlo al objeto de respuesta, res. Incluso podemos suscribirnos a eventos de error y manejarlos a medida que ocurren. Es un método mucho mejor para manejar archivos una vez que aprende a usarlo correctamente. Para un ejemplo más completo y una explicación del código anterior, consulta este artículo sobre creando servidores de archivos estáticos con Node.

Conclusión

De este artículo, debería haber aprendido los conceptos básicos de la lectura de archivos, así como algunos métodos avanzados de carga utilizando objetos Stream. Saber cuándo usarlos es la clave, y debe considerarse cuidadosamente para aplicaciones con limitaciones de memoria o de tiempo.

¿Cuál es su método preferido para manejar archivos? ¿Cómo has usado Streams en el pasado? ¡Cuéntanos en los comentarios!