4 - Módulos para administrar el sistema de archivos: 'fs' y 'fs/promises'


Ya sabemos como crear un módulo mínimo, como consumirlo a dicho módulo y también como consumir módulos que vienen por defecto en Node.js.

Ahora veremos dos módulos que vienen implementados en Node.js por defecto y nos permiten acceder al sistema de archivos para poder leer sus contenidos y crear otros archivos o carpetas.

Tenemos que poner mucho cuidado en entender el concepto de programación asincrónica que propone la plataforma de Node.js

La programación asincrónica busca no detener la ejecución del programa en forma completa por actividades que requieren mucho tiempo (una analogía es imaginar que nuestro entorno Node.js es un "mozo de restaurante" que va a una mesa y toma el pedido y lo envía a la cocina, la eleboración del pedido toma su tiempo pero el mozo no se queda congelado hasta que la cocina le avisa que el pedido está preparado sino que sigue tomando pedidos en otras mesas)

El módulo de administración de archivos "fs" implementa la programación asincrónica para procesar su creación, lectura, modificación, borrado etc. empleando el concepto de devolución de llamadas (funciones callbacks)

Creación de un archivo de texto.

Creemos un archivo llamado ejercicio5.js:

const fs = require('node:fs')

fs.writeFile('./archivo1.txt', 'línea 1\nLínea 2', error => {
  if (error)
    console.log(error)
  else
    console.log('El archivo fue creado')
})

console.log('última línea del programa')

Es importante tener en cuenta que cuando ejecutamos este programa aparece en pantalla primero el mensaje:

última línea del programa

antes que:

El archivo fue creado

modulo fs writeFile node.io

Expliquemos como funciona el código de este programa, primero requerimos el módulo 'fs':

const fs = require('node:fs')

Llamamos a la función writeFile a través de la constante fs. Esta función tiene tres parámetros:

La programación asincrónica podemos ver que sucede al mostrar el mensaje 'última línea del programa' antes de informarnos que el archivo fue creado. Es decir que cuando llamamos a la función writeFile el programa no se detiene en esta línea hasta que el archivo se crea sino que continúa con las siguientes instrucciones.

En este programita en particular no tiene grandes ventajas utilizar la programación asíncrona ya que luego de llamar a la función writeFile solo procedemos a mostrar un mensaje por la consola, pero en otras circunstancias podríamos estar ejecutando más actividades que no dependieran de la creación de dicho archivo (por ejemplo ordenando un vector en memoria)

Lectura de un archivo de texto.

Creemos un archivo llamado ejercicio6.js:

const fs = require('node:fs')

fs.readFile('./archivo1.txt', (error, datos) => {
  if (error)
    console.log(error)
  else
    console.log(datos.toString())
})

console.log('última línea del programa')

El resultado de ejecutar este programa es:

modulo fs readFile node.io

Tenemos en pantalla la impresión de las dos líneas del archivo de texto. El módulo 'fs' tiene una función llamada readFile que le pasamos como primer parámetro el nombre del archivo a leer y como segundo parámetro una función anónima que se ejecutará cuando se termine de leer el archivo pasando como parámetros un objeto con la referencia del error si lo hubiera y un objeto de tipo Buffer con todos los datos del archivo de texto.

Para mostrar el contenido del Buffer en formato texto llamamos al método toString(). Si no hacemos esto en pantalla mostrará los valores numéricos de los caracteres.

Nuevamente estamos implementando la lectura de un archivo en forma asincrónica, con el objeto de no detener el hilo de nuestro programa (esto es muy útil si el archivo a leer es de gran tamaño)

Otra forma de definir la función que se dispara luego de leer o escribir un archivo.

El empleo de funciones anónimas en JavaScript es muy común pero podemos volver a codificar el problema anterior pasando el nombre de una función:

Modificamos el archivo ejercicio6.js eliminando la función anónima e implementando una función con un nombre explícito:

const fs = require('node:fs')

function leer(error, datos) {
  if (error)
    console.log(error)
  else
    console.log(datos.toString())
}

fs.readFile('./archivo1.txt', leer)

console.log('última línea del programa')

Tengamos en cuenta que el resultado es idéntico a la implementación con la función anónima.

No es obligatorio que la implementación de la función esté definida antes de llamar a readFile, podría estar implementada al final:

const fs = require('node:fs')

fs.readFile('./archivo1.txt', leer)

console.log('última línea del programa')

function leer(error, datos) {
  if (error)
    console.log(error)
  else
    console.log(datos.toString())
}

O inclusive podría estar implementada en otro módulo y requerirla.

El módulo 'fs' tiene muchas funciones más además de crear y leer un archivo como hemos visto como pueden ser borrar, renombrar, crear directorios, borrar directorios, retornar información de archivos etc. para consultar estas funciones podemos visitar el api de Node.js

Administrar archivos mediante promesas.

A partir de la versión 10 de Node.js se presenta el acceso a archivos mediante la clase Promise en lugar de los callbacks (funciones de retorno)

Las promesas en JavaScript prestan una gran ayuda cuando implementamos algoritmos que requieren múltiples accesos a recursos asíncronos encadenados. Si bien podemos seguir utilizando callbacks el empleo de la clase Promise nos permitirá implementar algoritmos más legibles.

Las promesas son objetos de JavaScript que representan una eventual finalización o falla de una operación asincrónica.

Una diferencia clave entre los callbacks y los promesas es que cuando usamos el enfoque de devolución de llamada (callbacks), normalmente solo pasaríamos una devolución de llamada a una función que luego sería llamada al finalizar para obtener el resultado de algo. Sin embargo, en las promesas, adjunta devoluciones de llamada en el objeto de la promesa devuelta.

Veamos la sintaxis para acceder al módulo 'fs/promises' que propone Node.js con el ejemplo visto anteriormente.

Creemos un archivo llamado ejercicio5b.js:

const fs = require('node:fs/promises')

fs.writeFile('./archivo1b.txt', 'línea 1\nLínea 2\n creado con promesas.')
  .then(() => {
    console.log('El archivo de texto fue creado empleando promesas')
  })
  .catch(error => {
    console.log(error)
  })

console.log('última línea del programa')

En este caso la función 'writeFile' del módulo 'fs/promises' retorna una promesa y en el método then procesamos la respuesta correcta de la promesa y en el caso de no poderse crear el archivo se ejecuta el método catch.

Para la lectura del archivo de texto disponemos la siguiente sintaxis:

Creemos un archivo llamado ejercicio6b.js:

const fs = require('node:fs/promises')

fs.readFile('./archivo1b.txt')
  .then(datos => {
    console.log(datos.toString())
  })
  .catch(error => {
    console.log(error)
  })

console.log('última línea del programa')

De forma similar en el método then procesamos los datos del archivo de texto leido.

El código para imprimir todos los archivos del directorio donde se encuentra la aplicación utilizando promesas es ejercicio6c.js:

const fs = require('node:fs/promises')

fs.readdir('./')
  .then(archivos => {
    for (let archivo of archivos) {
      console.log(archivo)
    }
  })
  .catch(error => {
    console.log(error)
  })

Como vemos al método then llega un array con la lista de archivos del directorio actual './':

readdir fs/promises node.js

Sin ningún problema también podemos utilizar async await de JavaScript para procesar promesas (ejercicio6d.js):

const fs = require('node:fs/promises')

async function leerDirectorio() {
  const archivos = await fs.readdir("./")
  for (let archivo of archivos) {
    console.log(archivo)
  }
}

leerDirectorio()

En conceptos sucesivos seguiremos utilizando los paquetes 'fs' y 'fs/promises' he iremos introduciendo otras funcionalidades.

Retornar