Desenvolvimento

11 set, 2017

Node.js – 6 dicas do módulo File System

Publicidade
Node.js: 6 dicas do módulo File System
Node.js File System

Neste artigo, vou mostrar algumas dicas de um dos módulos principais do Node.js que é o File System, mais conhecido como ‘fs’.

Este módulo fornece operações de I/O (Input/Output ou E/S, Entrada/Saída) através de wrappers simples ao redor de funções POSIX. Para usar o módulo fs (File System) você deve usar o comando require (‘fs’), sendo que todos os métodos possuem versões assíncronas e síncronas.

Veremos neste artigo:

  1. fs assíncrono ou síncrono?
  2. Use file streams
  3. Cópia de arquivos
  4. Não use fs.access
  5. Limitações do fs.watch
  6. Módulos adicionais ao fs

 

Vamos lá!

#1 – fs assíncrono ou síncrono?

Você sempre deve procurar usar a API assíncrona quando desenvolver código que vai para produção, uma vez que esta API Não bloqueia o event loop e permite que você construa aplicações com maior performance. Um exemplo de código usando ‘fs’ assíncrono pode ser visto abaixo:

const fs = require('fs')
fs.unlink('/tmp/hello', (err) => {
 if (err) {
 return console.log(err)
 }
 console.log('successfully deleted /tmp/hello')
 })

Já a API síncrona pode ser usada quando estiver construindo provas de conceito ou pequenas aplicações. O código abaixo faz a mesma coisa que o anterior, mas usando a variante síncrona da mesma função:

const fs = require('fs')
try {
 fs.unlinkSync('/tmp/hello')
 } catch (ex) {
 console.log(ex)
 }

console.log(‘successfully deleted /tmp/hello’);

#2 – Use File Streams

Infelizmente é muito comum ver desenvolvedores de várias linguagens negligenciando o uso de File Streams. Streams em Node.js são conceitos poderosos que lhe permitem manter um consumo baixíssimo de memória e alta performance nas aplicações que precisavam ler e escrever arquivos.

Streams são estruturas Node.js que lhe permitem manipular dados. Existem três conceitos importantes que você deve entender:

  • source – o objeto de onde seus dados vêm;
  • pipeline – por onde seus dados passam (você pode filtrar ou modificar eles aqui);
  • sink – onde seus dados vão parar (terminam);

 

Um guia completo sobre Streams pode ser encontrado neste link.

#3 – Cópia de arquivos

Uma vez que o módulo fs não expõe uma função para copiar arquivos, você pode facilmente fazê-lo com streams:

const fs = require('fs')
 const readableStream = fs.createReadStream('original.txt')
 var writableStream = fs.createWriteStream('copy.txt')
readableStream.pipe(writableStream)

Uma das vantagens de se usar streams para isso é que você consegue transformar os arquivos enquanto eles estão sendo copiados, como por exemplo, fazer uma descompressão:

const fs = require('fs')
 const zlib = require('zlib')
fs.createReadStream('original.txt.gz')
 .pipe(zlib.createGunzip())
 .pipe(fs.createWriteStream('original.txt'))

#4 – Não use fs.access

O propósito da função fs.access é verificar se um usuário possui permissões para um dado arquivo ou caminho, algo como isto:

fs.access('/etc/passwd', fs.constants.R_OK | fs.constants.W_OK, (err) => {
 if (err) {
 return console.error('no access')
 }
 console.log('access for read/write')
 })

Note que o segundo parâmetro espera uma ou mais constantes para verificação de permissão. São elas:

  • fs.constants.F_OK – verifica se o path é visível para esse processo;
  • fs.constants.R_OK – verifica se o path pode ser lido por esse processo;
  • fs.constants.W_OK – verifica se o path pode ser escrito por esse processo;
  • fs.constants.X_OK – verifica se o path pode ser executado por esse processo;

 

Entretanto, note que usar fs.access para verificar a acessibilidade de um arquivo antes de chamar fs.open, fs.readFile ou fs.writeFile não é recomendado e a razão é simples: se você o fizer, outro processo pode alterar o arquivo entre a sua verificação e a ação de usar o arquivo propriamente dita.

Ao invés de verificar, tente usar o arquivo diretamente e trate os possíveis erros que podem acontecer.

#5 – Limitações do fs.watch

A função fs.watch é usada para escutar/observar por alterações em um arquivo ou pasta. No entanto a API fs.watch não é 100% consistente entre as diferentes plataformas e em alguns sistemas nem mesmo é disponível:

  • no Linux, usa inotify;
  • no BSD, usa kqueue;
  • no OS X, usa kqueue para arquivos e FSEvents para pasras;
  • no SunOS (incluindo Solaris e SmartOS), usa event ports;
  • no Windows, esta feature depend de ReadDirectoryChangesW;

 

Note também que a opção recursiva somente é suportada no OS X e Windows, mas não no Linux.

Além disso, o argumento filename no callback do watch nem sempre é fornecido (além de somente ser suportado no Linux e Windows), logo, prepare-se com fallbacks em caso dele vir undefined:

fs.watch('some/path', (eventType, filename) => {
 if (!filename) {
 //filename não foi fornecido
 }
 })

#6 – Módulos adicionais ao fs

Muitos desenvolvedores não concordam exatamente como o módulo fs funciona e criaram versões alternativas e muito úteis. Outros criam extensões ao fs para expandir as funcionalidades do módulo original. Alguns módulos bem famosos com essas características são:

graceful-fs

Este módulo é um substituto ao fs com algumas melhorias:

  • chamadas de open e readdir são enfileiradas e são automaticamente retentadas em caso de erros como EMFILE;
  • ignora erros EINVAL e EPERM em chown, fchown ou lchown se o usuário não é root;
  • faz com que lchmod e lchown se tornem noops, em caso de indisponibilidade;
  • retenta a leitura do arquivo se o read retornar erro EAGAIN;

 

Você pode usar esse módulo assim como usaria o módulo fs ou alternativamente substituindo o módulo globalmente:

// uso igual ao do fs
 const fs = require('graceful-fs')
// substituindo o original
 const originalFs = require('fs')
 const gracefulFs = require('graceful-fs')
 gracefulFs.gracefulify(originalFs)

mock-fs

O módulo mock-fs permite ao módulo fs original usar a memória RAM como se fosse um disco temporário para mockar o sistema de arquivos e facilitar os testes. Ele é bem fácil de usar:

const mock = require('mock-fs')
 const fs = require('fs')
mock({
 'caminho/para/pasta/falsa': {
 'algum-arquivo.txt': 'conteúdo do arquivo',
 'pasta-vazia': {}
 },
 'caminho/para/foto.png': new Buffer([8, 6, 7, 5, 3, 0, 9])
 })
fs.exists('caminho/para/pasta/falsa', function (exists) {
 console.log(exists)
 // vai imprimir true
 })

lockfile

File locking é uma maneira de restringir o acesso a um arquivo permitindo somente um processo acessar o arquivo de cada vez. Isto previne problemas de concorrência a um determinado arquivo atuando como um semáforo. Adicionar lockfiles usando o módulo lockfile é bem simples (como tudo em Node, aliás):

const lockFile = require('lockfile')
lockFile.lock('algum-arquivo.lock', function (err) {
 // se err existir, não foi possível bloquear o arquivo.
 // se err não existir, então o arquivo foi criado e não
 // será excluído até que você desbloqueie
// então, mais tarde, faça:
 lockFile.unlock('algum-arquivo.lock', function (err) {
})
 })

fs-extra

É um substituto ao fs tradicional adicionando novas funções e suporte a promises. Foi criado pois o autor afirma que estava cansado de ficar usando mkdirp, rimraf e ncp em seus projetos. Como um autêntico substituto, possui todos as funções originais do fs além de novas funções.

const fs = require('fs-extra')
// Async com promises:
 fs.copy('/tmp/myfile', '/tmp/mynewfile')
 .then(() => console.log('success!'))
 .catch(err => console.error(err))
// Async com callbacks:
 fs.copy('/tmp/myfile', '/tmp/mynewfile', err => {
 if (err) return console.error(err)
 console.log('success!')
 })
// Sync:
 try {
 fs.copySync('/tmp/myfile', '/tmp/mynewfile')
 console.log('success!')
 } catch (err) {
 console.error(err)
 }

Estas foram algumas dicas relacionados ao módulo File System (fs) do Node.js. E aí, conhece alguma que eu não listei? Deixa aí nos comentários!