Neste artigo, vou lhe 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
Saiba também que em meu curso de Node.js e MongoDB nós usamos bastante o módulo fs em uma das etapas iniciais do curso.
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:
1 2 3 4 5 6 7 8 9 10 |
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:
1 2 3 4 5 6 7 8 9 10 11 |
const fs = require('fs') try { fs.unlinkSync('/tmp/hello') } catch (ex) { console.log(ex) } console.log('successfully deleted /tmp/hello'); |
Agora se você deseja transformar em assíncrono mesmo as funções tradicionalmente síncronas do fs, sugiro usar a função promisify da biblioteca util, transformando qualquer função “callback-based” em promises, além de oferecer suporte a async/await. Mas espere, este recurso funciona apenas à partir da versão 8 do Node.
A imagem abaixo, cortesia do Armando Magalhães do grupo de Node.js do Facebook, mostra como funciona:
#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:
1 2 3 4 5 6 7 |
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:
1 2 3 4 5 6 7 8 |
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:
1 2 3 4 5 6 7 8 |
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:
1 2 3 4 5 6 7 |
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:
1 2 3 4 5 6 7 8 9 |
// 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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!
Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.