Introducción: ¿para qué sirven las colas de tareas en JavaScript?
\nEn el desarrollo web moderno con Node.js, a menudo surgen tareas que no pueden ejecutarse instantáneamente dentro de una única solicitud HTTP. Envío de correos electrónicos masivos, generación de informes PDF, procesamiento de imágenes, sincronización con API externas: todas estas operaciones requieren tiempo y recursos. Si se ejecutan de forma síncrona, el usuario esperará demasiado tiempo por la respuesta y el servidor podría sobrecargarse.
\nLas colas de tareas (Job Queues) resuelven este problema. Permiten aplazar la ejecución de operaciones pesadas, distribuir la carga, garantizar el reprocesamiento en caso de fallos y escalar el sistema horizontalmente. En este artículo, analizaremos los aspectos prácticos del trabajo con colas en JavaScript (Node.js): desde la teoría hasta la escritura de código funcional utilizando bibliotecas populares.
\n\nConceptos básicos de las colas de tareas
\nAntes de pasar al código, es importante comprender los elementos clave de cualquier cola de tareas:
\n- \n
- Job (tarea) — unidad de trabajo que debe ejecutarse. Generalmente contiene datos (payload) e metainformación (prioridad, intentos, estado). \n
- Queue (cola) — estructura de datos (a menudo basada en Redis) que almacena las tareas en orden de llegada. \n
- Worker (trabajador) — proceso o hilo que toma las tareas de la cola y las ejecuta. \n
- Producer (productor) — parte del sistema que añade tareas a la cola. \n
- Consumer (consumidor) — sinónimo de trabajador, que procesa las tareas. \n
En Node.js, las bibliotecas más populares para trabajar con colas son Bull y Kue. Ambas utilizan Redis como broker de mensajes. Bull es más moderna y eficiente, mientras que Kue es más sencilla para empezar. En este artículo nos centraremos en Bull, como la solución recomendada para producción.
\n\nImplementación práctica de una cola con Bull
\nInstalación y configuración
\nPara empezar, instale Redis (localmente o mediante Docker) y añada la biblioteca al proyecto:
\nnpm install bull\nLa inicialización básica de una cola se ve así:
\nconst Queue = require('bull');\nconst imageQueue = new Queue('image processing', {\n redis: { host: 'localhost', port: 6379 }\n});\n\nAdición de tareas (Producer)
\nEl productor añade una tarea con datos y opciones:
\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 intentos en caso de error\n backoff: 5000, // retardo entre intentos (ms)\n removeOnComplete: true // eliminar la tarea tras éxito\n });\n res.json({ jobId: job.id });\n});\n\nProcesamiento de tareas (Worker)
\nEl trabajador es un proceso separado (o el mismo proceso, pero asíncrono) que escucha la cola:
\nimageQueue.process(async (job, done) => {\n try {\n const { imageUrl, userId } = job.data;\n console.log(`Procesando imagen ${imageUrl} para el usuario ${userId}`);\n // Aquí su lógica: descargar, redimensionar, guardar\n await processImage(imageUrl);\n done(); // señal de finalización exitosa\n } catch (error) {\n done(new Error('Error de procesamiento: ' + error.message));\n }\n});\n\nManejo de errores y reintentos
\nBull reintenta automáticamente la tarea si el trabajador finaliza con un error. Puede configurar la estrategia de reintentos:
\nconst job = await imageQueue.add(data, {\n attempts: 5,\n backoff: { type: 'exponential', delay: 2000 }\n});\nTambién puede escuchar eventos de la cola para monitoreo:
\nimageQueue.on('completed', (job, result) => {\n console.log(`Tarea ${job.id} completada`);\n});\nimageQueue.on('failed', (job, err) => {\n console.error(`Tarea ${job.id} falló: ${err.message}`);\n});