
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:
- fs assíncrono ou síncrono?
- Use file streams
- Cópia de arquivos
- Não use fs.access
- Limitações do fs.watch
- 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:
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)
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 })
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) { }) })
É 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!