Trabalhando com Filas de Tarefas em JavaScript (Node.js)
Em aplicações web modernas e arquiteturas de microsserviços, frequentemente surge a necessidade de executar tarefas em segundo plano que não devem bloquear o fluxo principal de execução. Filas de tarefas são uma ferramenta poderosa que ajuda a gerenciar operações assíncronas, garantindo confiabilidade, escalabilidade e tolerância a falhas. Neste artigo, vamos detalhar como trabalhar com filas de tarefas no ambiente Node.js, explorar as principais bibliotecas e escrever exemplos práticos de código.
O que é uma fila de tarefas e por que ela é necessária?
Uma fila de tarefas (Task Queue) é uma estrutura de dados que opera pelo princípio FIFO (First In, First Out — "o primeiro a entrar é o primeiro a sair"). As tarefas são colocadas na fila e processadas por workers na ordem de chegada. Os principais cenários de uso incluem:
- Envio de notificações por email — não é necessário fazer o usuário esperar enquanto o email é enviado.
- Geração de relatórios ou PDFs — operações que consomem muitos recursos e são melhor executadas de forma assíncrona.
- Processamento de imagens enviadas — redimensionamento, compressão, adição de marcas d'água.
- Integração com APIs externas — execução de requisições com limitação de frequência (rate limiting).
- Tarefas agendadas (cron) — limpeza regular do banco de dados ou atualização de cache.
O uso de filas permite desafogar o servidor principal, aumentar a tolerância a falhas (se um worker falhar, a tarefa é passada para outro) e garantir a execução da tarefa mesmo em caso de falhas temporárias.
Fila simples em memória no Node.js
Para começar, vamos implementar uma fila simples sem dependências externas. Essa abordagem é adequada para prototipagem ou projetos muito pequenos, mas não é recomendada para produção devido à falta de persistência.
class SimpleQueue { constructor() { this.queue = []; this.processing = false; }
enqueue(task) { this.queue.push(task); this.processNext(); }
async processNext() { if (this.processing || this.queue.length === 0) return; this.processing = true; const task = this.queue.shift(); try { await task(); } catch (err) { console.error('Erro ao executar tarefa:', err); } finally { this.processing = false; this.processNext(); } }}
// Exemplo de usoconst queue = new SimpleQueue();
queue.enqueue(async () => { console.log('Tarefa 1: Enviando email');});
queue.enqueue(async () => { console.log('Tarefa 2: Gerando relatório');});
console.log('Tarefas adicionadas à fila');Este código demonstra a mecânica básica: as tarefas são executadas sequencialmente, uma após a outra. No entanto, ao reiniciar o servidor, todas as tarefas não processadas serão perdidas.
Bull — fila profissional baseada em Redis
Para projetos sérios em Node.js, o padrão de facto é a biblioteca Bull. Ela usa Redis como backend, o que garante persistência, suporte a prioridades, novas tentativas e tarefas atrasadas.
Instalação:
npm install bullExemplo simples de produtor e consumidor:
const Queue = require('bull');
// Criando uma fila com o nome 'email-queue'const emailQueue = new Queue('email-queue', { redis: { host: 'localhost', port: 6379 }});
// Função worker: processa as tarefasemailQueue.process(async (job) => { const { to, subject, body } = job.data; console.log(`Enviando email para ${to}: ${subject}`); // Aqui estaria o código real de envio de email await new Promise(resolve => setTimeout(resolve, 1000)); return { success: true };});
// Adicionando tare