Introdução: por que precisamos de filas de tarefas em JavaScript?
\nNo desenvolvimento web moderno com Node.js, frequentemente surgem tarefas que não podem ser concluídas instantaneamente dentro de uma única requisição HTTP. Envio de e-mails em massa, geração de relatórios em PDF, processamento de imagens, sincronização com APIs externas — todas essas operações exigem tempo e recursos. Se executadas de forma síncrona, o usuário esperaria muito tempo pela resposta e o servidor poderia ficar sobrecarregado.
\nAs filas de tarefas (Job Queues) resolvem esse problema. Elas permitem adiar a execução de operações pesadas, distribuir a carga, garantir o reprocessamento em caso de falhas e escalar o sistema horizontalmente. Neste artigo, abordaremos os aspectos práticos de trabalhar com filas em JavaScript (Node.js): da teoria à escrita de código funcional usando bibliotecas populares.
\n\nConceitos principais de filas de tarefas
\nAntes de ir para o código, é importante entender os elementos-chave de qualquer fila de tarefas:
\n- \n
- Job (tarefa) — unidade de trabalho a ser executada. Geralmente contém dados (payload) e metainformações (prioridade, tentativas, status). \n
- Queue (fila) — estrutura de dados (frequentemente baseada em Redis) que armazena tarefas na ordem de chegada. \n
- Worker (trabalhador) — processo ou thread que retira tarefas da fila e as executa. \n
- Producer (produtor) — parte do sistema que adiciona tarefas à fila. \n
- Consumer (consumidor) — sinônimo de worker, que processa as tarefas. \n
Em Node.js, as bibliotecas mais populares para trabalhar com filas são Bull e Kue. Ambas usam Redis como broker de mensagens. Bull é mais moderna e performática, Kue é mais simples para iniciar. Neste artigo, focaremos no Bull, como a solução recomendada para produção.
\n\nImplementação prática de uma fila com Bull
\nInstalação e configuração
\nPara começar, instale o Redis (localmente ou via Docker) e adicione a biblioteca ao projeto:
\nnpm install bull\nA inicialização básica da fila fica assim:
\nconst Queue = require('bull');\nconst imageQueue = new Queue('processamento de imagens', {\n redis: { host: 'localhost', port: 6379 }\n});\n\nAdicionando tarefas (Producer)
\nO produtor adiciona uma tarefa com dados e opções:
\napp.post('/upload', async (req, res) => {\n const job = await imageQueue.add({\n imageUrl: req.body.url,\n userId: req.user.id\n }, {\n attempts: 3, // número de tentativas em caso de erro\n backoff: 5000, // atraso entre tentativas (ms)\n removeOnComplete: true // remover tarefa após sucesso\n });\n res.json({ jobId: job.id });\n});\n\nProcessando tarefas (Worker)
\nO worker é um processo separado (ou o mesmo processo, mas assíncrono) que escuta a fila:
\nimageQueue.process(async (job, done) => {\n try {\n const { imageUrl, userId } = job.data;\n console.log(`Processando imagem ${imageUrl} para o usuário ${userId}`);\n // Sua lógica aqui: baixar, redimensionar, salvar\n await processImage(imageUrl);\n done(); // sinal de conclusão bem-sucedida\n } catch (error) {\n done(new Error('Erro de processamento: ' + error.message));\n }\n});\n\nTratamento de erros e novas tentativas
\nO Bull automaticamente repete a tarefa se o worker terminar com erro. Você pode configurar a estratégia de novas tentativas:
\nconst job = await imageQueue.add(data, {\n attempts: 5,\n backoff: { type: 'exponential', delay: 2000 }\n});\nVocê também pode ouvir eventos da fila para monitoramento:
\nimageQueue.on('completed', (job, result) => {\n console.log(`Tarefa ${job.id} concluída`);\n});\nimageQueue.on('failed', (job, err) => {\n console.error(`Tarefa ${job.id} falhou: ${err.mes